Introduction

Computer science is a field that is growing rapidly in the United States and around the world today. Industry is constantly releasing advancements in computer science and technology is becoming more ingrained into our daily lives with each passing day. Therefore, with an increase in technology usage, the demand for computer scientists has increased in popularity. Due to this fast-growing field, educational institutions and systems are increasing the amount of computer science courses offered in order to train more future computer scientists.

These increases started at the college level, where majoring in computer science is now becoming a widely available option. At Macalester College, it is one of the largest departments for both students and faculty. While the availability of courses at the college level is a good start, there is a big push to have computer science courses offered in K-12 education. Offering computer science courses in elementary and secondary schools provides an opportunity for kids to expose themselves to coding, potentially leading to younger students discovering new interests and actively engaging with computer science earlier. Oftentimes, being exposed to computer science at a younger age can make students more comfortable with the material and the field later on, which can contribute to a more empowered and diverse set of students entering the workforce or higher education. Given the importance of having computer science courses available in K-12 education, we decided to investigate the availability of computer science courses in K-12 school districts in Minnesota. In this project, we explore the connection between a variety of data sets related to this topic, including K-12 computer science course availability in Minnesota, demographic information from the U.S. census, ACT scores, and school district financial information.

To see our final product, visit: https://advanced-data-science-final-project.netlify.app

About the Data Sets

Minnesota Common Course Catalogue

This data set is provided by the Minnesota State Department of Education and is available to the public. The Minnesota Common Course Catalogue (MCCC) presents information about all the courses that are offered across school districts in Minnesota. For our purposes, we are looking at subject area 10, computer and information sciences (K-12), and all categories and classifications. For each course classification, there is information about the district where it is taught, the region of the state, the local course title, whether it is an AP or IB course, if it fulfills a graduation requirement, among others. We joined this data set with information about what category the course classification falls into, such as A - computer literacy, B - management information systems, or C - network systems. The final data set has 1,602 computer and information classes taught across the state in K-12 education.

Link: https://public.education.mn.gov/MDEAnalytics/DataTopic.jsp?TOPICID=84

American Community Survey

We accessed the 2019 American Community Survey (ACS) from the tidycensus package in R. The ACS is an annual demographic survey from the U.S. Census Bureau. We used this data set to find by-district information about race, socioeconomic status, living environments, and other relevant variables for our analyses and visualizations. We hypothesized that computer science course availability would be connected to one or more of these variables, due to the trend where more computer science courses are offered in wealthier areas across the country that have more resources. We are using this data set to further investigate that possibility.

ACT Data

We retrieved this data from the Minnesota State Department of Education, which gives us average composite ACT scores for each school district in Minnesota for a range of years. It has been proven that higher ACT scores are connected to wealth, which would lead us to expect that districts with more resources would have higher average ACT scores. As aforementioned, we predict that computer science course availability is greater in areas with more wealth and resources, therefore we thought these scores may be another interesting variable in predicting or visualizing availability of computer science courses by district. In the same way we expect certain demographic information to be correlated with computer science availability, we are hoping to use average ACT score information to present another method of showing the connection.

Link: https://public.education.mn.gov/MDEAnalytics/DataTopic.jsp?TOPICID=87

Annual Survey of School System Finances

Another important set of data that we used from the U.S. Census Bureau is the Annual Survey of School System Finances (ASSSF). This data contains information about 2018 financial activity of public elementary and secondary school systems for all states across the country. In particular, we were interested in discovering if funding, revenue, or spending for a district was correlated to availability of computer science courses. Due to the fact that public school funding often comes from income taxes, wealthier areas provide more funding for their local schools.

Link: https://www.census.gov/programs-surveys/school-finances.html

Minnesota School Districts

This data set includes the name of all school districts in Minnesota as well as their district numbers, which was created in 2015. This data came from the Minnesota State Department of Human Services. We joined the district numbers from this data set to the American Community Survey data, which only had the district names. Accessing district numbers and names was integral in connecting our various data sets because the common piece of information between our ideas is district information.

Link: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/dhs16_193591.pdf

Important Packages and Data Sets

Load Packages

library(tidycensus) # for getting the census data
library(tidyverse) # for data cleaning and visualization
library(sf) # for mapping
library(tidymodels)
library(naniar)
library(DALEX)
library(DALEXtra)
library(vip)
library(gridExtra)
library(plotly)

Load Data Sets

act <- read_csv("act_data.csv") # ACT
districts <- read_csv("districts.csv") # MN School Districts
course_catalog <- read_csv("course_catalog.csv") # MCCC
ss <- readxl::read_excel("elsec18t.xls") # ASSSF

Data Cleaning

MCCC Data

The data from the Department of Education was neatly split into spreadsheets based on course classifications. In order to use this data more easily, we manually combined all of the spreadsheets into one data set with all of the courses offered. We had to manually add in the columns for course classification and category in order to create one data set. Furthermore, in order to connect to the other data sets, we needed to have the district number as its own variable. In this data set, the district name and number were combined as one variable, so we used regular expressions to split the name and number. This was fairly simple, due to the district variable consistently having the number as the last 7 digits.

course_catalog_predictors <-
course_catalog %>%
  mutate(DistNum = as.integer(str_sub(`District`, -7,-4))) %>%
  group_by(DistNum) %>%
  summarise(TotalClasses = sum(n()),
            NumCat = length(unique(Category)))

ACS Data

The ACS data was already in a tidy format, which was convenient for our purposes. We focused on narrowing down variables to the ones that were important to our project and renaming them so that we could easily understand the meaning of the variable. Many of the districts had the word ‘Minnesota’ in it, so we removed that because all of our data is about Minnesota. We also created some percentage variables because they would be more meaningful for our exploration than the raw numbers that tidycensus provided us with.

# census_api_key("key")

mn_2019_census <- get_acs(state = "MN", 
                         geography = "school district (unified)",
                         variables = c("B01003_001",
                                       "B01001_001",
                                       "B01001_004",
                                       "B01001_005",
                                       "B01001_006",
                                       "B02001_001",
                                       "B02001_002",
                                       "B02001_003",
                                       "B02001_005",
                                       "B02001_006",
                                       "B02001_008",
                                       "B19013_001",
                                       "B09010_001",
                                       "B09010_002", 
                                       "B25031_001",
                                       "B25027_001",
                                       "B25027_002",
                                       "B28002_001",
                                       "B28002_002"),
                         geometry = TRUE,
                         year = 2019)
## 
  |                                                                            
  |                                                                      |   0%
  |                                                                            
  |=                                                                     |   1%
  |                                                                            
  |==                                                                    |   2%
  |                                                                            
  |==                                                                    |   3%
  |                                                                            
  |===                                                                   |   4%
  |                                                                            
  |===                                                                   |   5%
  |                                                                            
  |====                                                                  |   5%
  |                                                                            
  |====                                                                  |   6%
  |                                                                            
  |=====                                                                 |   7%
  |                                                                            
  |=====                                                                 |   8%
  |                                                                            
  |======                                                                |   8%
  |                                                                            
  |======                                                                |   9%
  |                                                                            
  |========                                                              |  11%
  |                                                                            
  |=========                                                             |  12%
  |                                                                            
  |==========                                                            |  14%
  |                                                                            
  |===========                                                           |  15%
  |                                                                            
  |===========                                                           |  16%
  |                                                                            
  |============                                                          |  17%
  |                                                                            
  |============                                                          |  18%
  |                                                                            
  |=============                                                         |  19%
  |                                                                            
  |==============                                                        |  20%
  |                                                                            
  |==============                                                        |  21%
  |                                                                            
  |===============                                                       |  22%
  |                                                                            
  |=================                                                     |  24%
  |                                                                            
  |==================                                                    |  25%
  |                                                                            
  |===================                                                   |  27%
  |                                                                            
  |====================                                                  |  28%
  |                                                                            
  |=====================                                                 |  30%
  |                                                                            
  |======================                                                |  31%
  |                                                                            
  |=======================                                               |  33%
  |                                                                            
  |========================                                              |  34%
  |                                                                            
  |=========================                                             |  36%
  |                                                                            
  |==========================                                            |  37%
  |                                                                            
  |===========================                                           |  39%
  |                                                                            
  |============================                                          |  40%
  |                                                                            
  |=============================                                         |  42%
  |                                                                            
  |==============================                                        |  42%
  |                                                                            
  |===============================                                       |  44%
  |                                                                            
  |=================================                                     |  48%
  |                                                                            
  |===================================                                   |  50%
  |                                                                            
  |===================================                                   |  51%
  |                                                                            
  |=====================================                                 |  53%
  |                                                                            
  |======================================                                |  55%
  |                                                                            
  |========================================                              |  57%
  |                                                                            
  |=========================================                             |  59%
  |                                                                            
  |===========================================                           |  61%
  |                                                                            
  |============================================                          |  63%
  |                                                                            
  |==============================================                        |  66%
  |                                                                            
  |================================================                      |  68%
  |                                                                            
  |=================================================                     |  70%
  |                                                                            
  |===================================================                   |  73%
  |                                                                            
  |=====================================================                 |  75%
  |                                                                            
  |======================================================                |  77%
  |                                                                            
  |========================================================              |  79%
  |                                                                            
  |=========================================================             |  81%
  |                                                                            
  |===========================================================           |  84%
  |                                                                            
  |============================================================          |  86%
  |                                                                            
  |==============================================================        |  88%
  |                                                                            
  |===============================================================       |  90%
  |                                                                            
  |================================================================      |  92%
  |                                                                            
  |==================================================================    |  94%
  |                                                                            
  |===================================================================   |  96%
  |                                                                            
  |===================================================================== |  99%
  |                                                                            
  |======================================================================| 100%
mn_2019_census_nogeom <- mn_2019_census %>%
  select(-`moe`) %>% #get rid of moe variable(not sure what it is)
  spread(variable, estimate) %>% #give each variable a column
  rename(total_pop = B01003_001,
         male_5to9 = B01001_004,
         male_10to14 = B01001_005,
         male_15to17 = B01001_006,
         race_white_only = B02001_002,
         race_Black_only = B02001_003,
         race_Asian_only = B02001_005,
         race_pacific_islander_only = B02001_006,
         at_least_two_races = B02001_008,
         med_household_inc_12mo = B19013_001,
         SSI_pubassist_foodstamps = B09010_002,
         med_gross_rent = B25031_001,
         house_units_w_mortgage = B25027_002,
         internet_subscrip_in_house = B28002_002
         ) %>% #rename variables
  mutate(District = str_extract(`NAME`, "[^,]+")) %>% #delete ", Minnesota" after district name
  select(-NAME) %>% #remove `NAME` column
  mutate(perc_male_5to9 = male_5to9/B01001_001,
         perc_male_10to14 = male_10to14/B01001_001,
         perc_male_15to17 = male_15to17/B01001_001,
         perc_white_only = race_white_only/B02001_001,
         perc_black_only = race_Black_only/B02001_001,
         perc_asian_only = race_Asian_only/B02001_001,
         perc_pacific_islander_only = race_pacific_islander_only/B02001_001,
         perc_at_least_two_races = at_least_two_races/B02001_001,
         perc_SSI_pubassist_foodstamps = SSI_pubassist_foodstamps/B09010_001,
         perc_house_units_w_mortgage = house_units_w_mortgage/B25027_001,
         perc_internet_subscription = internet_subscrip_in_house/B28002_001
         ) %>% #create percentage variables
  select(-B01001_001,-B02001_001, -B09010_001, -B25027_001, -B28002_001 ) #deselect variables used to calculate % but aren't important for our visualizations

ACT Data

This data set included a range of years, so we filtered the graduation year to our year of interest: 2018. This data set also originally had extra variables that were irrelevant to our interests, so our cleaning process involved removing predictors so that we were only left with district name and average composite ACT score. The district name included the district number, so in order to create two separate values we used regular expressions. The district number was consistently located at the same spot in the name, so splitting them was relatively straightforward.

act_clean <-
act %>%
  filter(`Grad Year` == 2018,
         `Analysis Level` == "District",
         `District Name` != "MINNESOTA DEPT OF EDUCATION",
         `Avg Comp` != ".") %>%
  select(`District Name`, `Avg Comp`) %>%
  mutate(`Dist Num` = as.integer(str_extract_all(`District Name`, "[:digit:]+"))) %>%
  mutate(`Dist Num`= replace(`Dist Num`, `District Name`=="GRANADA-HUNTLEY-EAST CHAIN SD", 2536),
         `Dist Num`= replace(`Dist Num`, `District Name`=="ALBERT LEA AREA SCHOOLS", 241),
         `Dist Num`= replace(`Dist Num`, `District Name`=="ATWATER-COSMOS-GROVE CITY SD", 2396),
         `Dist Num`= replace(`Dist Num`, `District Name`=="BOLD SCHOOL DISTRICT", 2534),
         `Dist Num`= replace(`Dist Num`, `District Name`=="BUFFALO HANOVER MONTROSE SD", 0877),
         `Dist Num`= replace(`Dist Num`, `District Name`=="BUFFALO LAKE-HECTOR-STEWART SD", 2159),
         `Dist Num`= replace(`Dist Num`, `District Name`=="EASTERN CARVER CO SCHOOLS", 112),
         `Dist Num`= replace(`Dist Num`, `District Name`=="MANKATO AREA PUBLIC SCHS", 77),
         `Dist Num`= replace(`Dist Num`, `District Name`=="MINNEAPOLIS PUBLIC SCH DIST", 9991),
         `Dist Num`= replace(`Dist Num`, `District Name`=="MINNEOTA PUBLIC SCHOOLS", 414),
         `Dist Num`= replace(`Dist Num`, `District Name`=="PLAINVIEW ELGIN MILLVILLE SD", 2899),
         `Dist Num`= replace(`Dist Num`, `District Name`=="ROYALTON PUBLIC SCHOOLS", 485),
         `Dist Num`= replace(`Dist Num`, `District Name`=="WASECA PUBLIC SCHOOL DIST", 829),
         `Dist Num`= replace(`Dist Num`, `District Name`=="WAUBUN-OGEMA-WHITE EARTH PSD", 435),
         `Dist Num`= replace(`Dist Num`, `District Name`=="WINONA AREA PUBLIC SCHOOL DIST", 861))

Annual Survey of School System Finances

Fortunately, we did not have to clean this data set. Originally, this data set included information about all of the states, but the process of data joining narrowed down the data to just contain information about Minnesota. Below is the code that allowed us to view the data entries related to Minnesota before going through the process of data joining.

ss %>%
  # filter for MN
  filter(str_detect(NCESID, "^27")) %>%
  arrange(NCESID) %>%
  head(10) %>%
  select(NCESID)

Minnesota School Districts

This data set originally had four categories: district name, district number, start date, and end date. To prepare this data set for joining, we removed the start and end date variables. In order to join it with the ACS data set, we had to compare the district names in both to ensure that they aligned. Only 16 district names didn’t match, which was a small number, so we manually changed the district names to match the names in the ACS data set.

Data Joining

Part of the work we did after finding, importing, and cleaning our various data sets was joining them together. An important piece for this was ensuring that the district names and numbers aligned, and most of that was completed in the previous step. Here is our code for joining the data sets:

# read in data sets
## annual survey of school system finances (2018, national)
ss <- readxl::read_excel("elsec18t.xls")

## selected variables from the ACS (2019, MN)
mn_acs <- st_read("mn_2019_census/mn_2019_census.shp", 
                  geometry_column = "geometry", 
                  fid_column_name = "geometry")
## Reading layer `mn_2019_census' from data source `/Users/anaelkuperwajs/STAT 494/STAT494-Final-Project/mn_2019_census/mn_2019_census.shp' using driver `ESRI Shapefile'
## Simple feature collection with 324 features and 28 fields (with 1 geometry empty)
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -97.23921 ymin: 43.49936 xmax: -89.49174 ymax: 49.38436
## Geodetic CRS:  NAD83
mn_acs <- as.data.frame(mn_acs)
# rename the columns
names(mn_acs) <- c("GEOID", "male_5to9", "male_10to14", "male_15to17",
                   "total_pop", "race_white_only", "race_Black_only", "race_Asian_only",
                   "race_pacific_islander_only", "at_least_two_races", "SSI_pubassist_foodstamps",
                   "med_household_inc_12mo", "house_units_w_mortgage", "med_gross_rent",
                   "internet_subscrip_in_house", "District", "perc_male_5to9", "perc_male_10to14",
                   "perc_male_15to17", "perc_white_only", "perc_black_only", "perc_asian_only",
                   "perc_pacific_islander_only", "perc_at_least_two_races", "perc_SSI_pubassist_foodstamps",
                   "perc_house_units_w_mortgage", "perc_internet_subscription",
                   "District.Nbr", "geometry")
# move the district name and number cols to the front
mn_acs <- mn_acs %>%
  relocate(District, District.Nbr, .after = GEOID)

## ACT scores (MN)
mn_act <- read.csv("act_clean.csv")[-1] # remove the redundant index column created when reading by csv

Join by NCES ID for school districts.

# look at this column from the school survey
ss %>%
  # filter for MN 
  filter(str_detect(NCESID, "^27")) %>%
  arrange(NCESID) %>%
  head(10) %>%
  select(NCESID)
# and from the ACS
mn_acs %>%
  arrange(GEOID) %>%
  head(10) %>%
  select(GEOID)
# left join on ACS data
mn_acs_ss <- mn_acs %>%
  left_join(ss, by = c("GEOID" = "NCESID"))

Let’s look at the name columns from the two data sets to verify that they point to the same district.

mn_acs_ss %>%
  select(District, NAME)

Now let’s see which rows from the ACS and school survey didn’t get a match.

# ACS
mn_acs_ss %>%
  filter(is.na(NAME))
# school survey
ss %>%
  filter(str_detect(NCESID, "^27")) %>% # filter for MN
  anti_join(mn_acs, by = c("NCESID" = "GEOID"))

So only the row “Remainder of Minnesota” from the ACS didn’t have a match, which is reasonable. There are 67 school districts from the school survey that did not join to the ACS, but perhaps they fall into the “Remainder of Minnesota” category. A lot of these districts also had 0 for enrollment.

To join this data set with the ACT data, we’ll have to use the school district numbers as the ACT data does not contain the NCES ID.

# look at this variable from each data set to see their format
mn_acs_ss %>%
  arrange(District.Nbr) %>%
  select(District.Nbr)
mn_act %>%
  arrange(Dist.Num) %>%
  select(Dist.Num)

It appears that we’re missing ACT scores for 11 school districts. Nevertheless, the formats do match so let’s join the data sets.

mn_acs_ss_act <- mn_acs_ss %>%
  mutate(District.Nbr = as.integer(District.Nbr)) %>%
  left_join(mn_act, by = c("District.Nbr" = "Dist.Num"))

Finally, before we can export the data, we will remove the redundant names and ID columns from the school survey and ACT data sets as we’ll stick to their format from the ACS.

mn_acs_ss_act <- mn_acs_ss_act %>%
  select(-c("IDCENSUS", "NAME", "District.Name"))

Export the data:

#write.csv(mn_acs_ss_act, "mn_acs_ss_act.csv")
st_write(mn_acs_ss_act, "mn_acs_ss_act/mn_acs_ss_act.shp", 
         append=FALSE, 
         fid_column_name = "geometry")
## Deleting layer `mn_acs_ss_act' using driver `ESRI Shapefile'
## Writing layer `mn_acs_ss_act' to data source `mn_acs_ss_act/mn_acs_ss_act.shp' using driver `ESRI Shapefile'
## Writing 324 features with 92 fields and geometry type Multi Polygon.

Join with predictor data, replace all missing data from the course catalog with 0, and remove the redundant variable. This is the final step!

mn_acs_ss_act_pred <- mn_acs_ss_act %>%
  # mutate(District.Nbr = as.integer(District.Nbr)) %>% ## no longer necessary since they're already ints
  ## changed to left join to ignore rows without matches from the course catalog
  left_join(course_catalog_predictors, by = c("District.Nbr" = "DistNum")) %>% 
  mutate(TotalClasses = ifelse(is.na(TotalClasses), 0, TotalClasses),
         NumCat = ifelse(is.na(NumCat), 0, NumCat)) 

Mapping and Shape Files

While we had all the data necessary to create useful graphs and tables, one of our goals was to create visualizations and maps. Our data is focused on the state of Minnesota and all of it is segmented by school districts. Therefore, we believed that an interesting and informative way to present this data is through maps. The piece that was missing for us to accomplish this was the shape files for the districts. Shape files are notoriously tricky, and implementing this was a challenge.

Once we added a column for the geometry of each district (which was necessary for mapping), we could no longer use the write and read_csv functions that we were so accustomed to. Unfortunately, we found this out the hard way. When we exported our final data set using write_csv() and read it in another file with read_csv(), our data values were completely altered. We hypothesized that this could be due to how each geometry feature is saved as a list of coordinates separated by commas, which would interfere with the csv (comma-separated values) format. Fortunately, we discovered that st_write() and st_read() from the sf package were the functions we needed to use instead. These were specifically made for exporting and importing files with simple geometry features. One issue we encountered with exporting shape files is that certain column names were abbreviated in the process. For example, male_5to9 became mal_5t9 and total_pop became totl_pp. In the end, we decided that the simplest and fastest solution was to rename our variables back to their original names after we read in the data.

##### RUN THIS CHUNK WHENEVER YOU READ IN THE DATA
mn_acs_ss_act_pred <- st_read("mn_acs_ss_act_pred/mn_acs_ss_act_pred.shp",
                geometry_column = "geometry",
                fid_column_name = "geometry",
                quiet = TRUE)
# rename the census variables because their names were reformatted...
names(mn_acs_ss_act_pred)[1:28] <- 
  c("GEOID", "District", "District.Nbr", "male_5to9", "male_10to14", "male_15to17",
    "total_pop", "race_white_only", "race_Black_only", "race_Asian_only",
    "race_pacific_islander_only", "at_least_two_races", "SSI_pubassist_foodstamps",
    "med_household_inc_12mo", "house_units_w_mortgage", "med_gross_rent",
    "internet_subscrip_in_house", "perc_male_5to9", "perc_male_10to14",
    "perc_male_15to17", "perc_white_only", "perc_black_only", "perc_asian_only",
    "perc_pacific_islander_only", "perc_at_least_two_races", "perc_SSI_pubassist_foodstamps",
    "perc_house_units_w_mortgage", "perc_internet_subscription")
names(mn_acs_ss_act_pred)[93] <- c("TotalClasses")

Visualizations and Plotly

Although we knew our maps up to this point were telling, we wanted to add another element to our project. We decided to make the maps interactive and chose to use plotly to achieve this. The plotly functions had an issue with our final data set and would not map unless we removed all rows with NA; converting the NA values to 0 did not fix the issue, and neither did converting the ‘N’ values we found in two variable columns to 0. In total, dropping the NA rows removed 11 districts. With more time, this issue should be explored further. Something that is important to note is that the districts that were not mapped still show interactivity based on whichever district is closest to the pointer.

Below we have two examples from our final product: one of a static graph and one of a plotly graph. Most of our visualizations and maps were built in the same way, so we did not include all of the visualization code in this document.

Static Graph

#population
ggplot(mn_acs_ss_act_pred, aes(fill = total_pop)) + 
  geom_sf(color = NA) + 
  scale_fill_viridis_c(option = "magma", labels = scales::comma) +
  labs(title = "Total Population", fill = "") +
  theme(axis.ticks = element_blank(),
        axis.text = element_blank(),
        panel.background = element_rect(fill = "white"),
        plot.title = element_text(hjust = 0.5))

Plotly

mn_acs_ss_act_pred_viz <- 
  mn_acs_ss_act_pred %>%  drop_na()

ggplotly(
  ggplot(mn_acs_ss_act_pred_viz) +
  geom_sf(aes(fill = TotalClasses, text = paste(District)),  size = .1, color = "white") +
  scale_fill_viridis_c(option = "B") +
  labs(title = "Total CS Courses Offered", fill = "") +
  theme(axis.ticks = element_blank(),
        axis.text = element_blank(),
        panel.background = element_rect(fill = "white"),
        plot.title = element_text(hjust = 0.5),
        title =element_text(size=12, face='bold'))
) 

Modeling

The goal of our model was to predict the number of computer science courses offered per district in Minnesota based on the variables from our ACS, ASSSF, and ACT data.

We first read in the data and cleaned up variable names, as they were abbreviated after being exported to shape file format.

# read in the data
mn <- st_read("mn_acs_ss_act_pred/mn_acs_ss_act_pred.shp",
              geometry_column = "geometry",
              fid_column_name = "geometry")
## Reading layer `mn_acs_ss_act_pred' from data source `/Users/anaelkuperwajs/STAT 494/STAT494-Final-Project/mn_acs_ss_act_pred/mn_acs_ss_act_pred.shp' using driver `ESRI Shapefile'
## Simple feature collection with 324 features and 94 fields (with 1 geometry empty)
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -97.23921 ymin: 43.49936 xmax: -89.49174 ymax: 49.38436
## Geodetic CRS:  NAD83
# rename the census variables because their names were reformatted...
names(mn)[1:28] <- 
  c("GEOID", "District", "District.Nbr", "male_5to9", "male_10to14", "male_15to17",
    "total_pop", "race_white_only", "race_Black_only", "race_Asian_only",
    "race_pacific_islander_only", "at_least_two_races", "SSI_pubassist_foodstamps",
    "med_household_inc_12mo", "house_units_w_mortgage", "med_gross_rent",
    "internet_subscrip_in_house", "perc_male_5to9", "perc_male_10to14",
    "perc_male_15to17", "perc_white_only", "perc_black_only", "perc_asian_only",
    "perc_pacific_islander_only", "perc_at_least_two_races", "perc_SSI_pubassist_foodstamps",
    "perc_house_units_w_mortgage", "perc_internet_subscription")
names(mn)[93] <- c("TotalClasses")

Data Exploration

# look at the variables and their types
str(mn)
## Classes 'sf' and 'data.frame':   324 obs. of  95 variables:
##  $ GEOID                        : chr  "2700001" "2700005" "2700006" "2700007" ...
##  $ District                     : chr  "Mountain Iron-Buhl School District" "United South Central School District" "Maple River School District" "Kingsland Public School District" ...
##  $ District.Nbr                 : int  712 2134 2135 2137 2142 2143 2144 2149 2154 2155 ...
##  $ male_5to9                    : num  146 220 207 133 370 176 625 344 241 266 ...
##  $ male_10to14                  : num  104 160 180 176 493 201 991 272 252 237 ...
##  $ male_15to17                  : num  91 126 133 91 239 93 530 179 132 144 ...
##  $ total_pop                    : num  4545 5790 5991 5023 17435 ...
##  $ race_white_only              : num  4251 5517 5802 4773 15739 ...
##  $ race_Black_only              : num  63 10 26 7 71 21 40 32 30 68 ...
##  $ race_Asian_only              : num  81 43 17 66 49 12 230 54 12 0 ...
##  $ race_pacific_islander_only   : num  0 0 0 0 19 3 0 0 0 0 ...
##  $ at_least_two_races           : num  104 154 137 133 604 70 369 126 111 183 ...
##  $ SSI_pubassist_foodstamps     : num  238 313 196 139 525 151 421 224 239 510 ...
##  $ med_household_inc_12mo       : num  50550 53699 64451 59087 57221 ...
##  $ house_units_w_mortgage       : num  756 1009 1074 1031 3604 ...
##  $ med_gross_rent               : num  625 640 636 675 709 769 929 616 677 670 ...
##  $ internet_subscrip_in_house   : num  1593 1891 1994 1689 6243 ...
##  $ perc_male_5to9               : num  0.0321 0.038 0.0346 0.0265 0.0212 ...
##  $ perc_male_10to14             : num  0.0229 0.0276 0.03 0.035 0.0283 ...
##  $ perc_male_15to17             : num  0.02 0.0218 0.0222 0.0181 0.0137 ...
##  $ perc_white_only              : num  0.935 0.953 0.968 0.95 0.903 ...
##  $ perc_black_only              : num  0.01386 0.00173 0.00434 0.00139 0.00407 ...
##  $ perc_asian_only              : num  0.01782 0.00743 0.00284 0.01314 0.00281 ...
##  $ perc_pacific_islander_only   : num  0 0 0 0 0.00109 ...
##  $ perc_at_least_two_races      : num  0.0229 0.0266 0.0229 0.0265 0.0346 ...
##  $ perc_SSI_pubassist_foodstamps: num  0.285 0.222 0.137 0.124 0.184 ...
##  $ perc_house_units_w_mortgage  : num  0.505 0.49 0.552 0.572 0.513 ...
##  $ perc_internet_subscription   : num  0.756 0.771 0.823 0.78 0.779 ...
##  $ CONUM                        : chr  "27137" "27043" "27013" "27045" ...
##  $ CSA                          : chr  "N" "N" "359" "462" ...
##  $ CBSA                         : chr  "20260" "N" "31860" "40340" ...
##  $ ENROLL                       : num  507 707 927 557 2007 ...
##  $ TOTALRE                      : num  8146 12242 13103 8078 39951 ...
##  $ TFEDREV                      : num  442 554 489 374 1860 503 835 848 545 955 ...
##  $ FEDRCOM                      : num  175 170 146 160 506 83 197 194 168 321 ...
##  $ FEDRSPE                      : num  0 0 0 0 0 228 0 0 0 0 ...
##  $ FEDRNUT                      : num  133 215 213 135 575 166 474 283 186 483 ...
##  $ FEDROTH                      : num  134 169 130 79 779 26 164 371 191 151 ...
##  $ TSTREV                       : num  5171 7246 9420 5269 22890 ...
##  $ STRFORM                      : num  3618 5554 7178 4295 17628 ...
##  $ STRSPEC                      : num  253 818 1379 339 2493 ...
##  $ STRTRAN                      : num  0 7 4 0 0 17 929 27 0 3 ...
##  $ STROTHR                      : num  1300 867 859 635 2769 ...
##  $ TLOCREV                      : num  2533 4442 3194 2435 15201 ...
##  $ LOCRTAX                      : num  294 3509 1903 1685 6434 ...
##  $ LOCRPRO                      : num  294 3509 1903 1685 6434 ...
##  $ LOCRPAR                      : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ LOCRCIC                      : num  1642 45 63 2 6756 ...
##  $ LOCROSC                      : num  37 4 151 48 406 103 1 32 54 117 ...
##  $ LOCRCHA                      : num  132 383 855 510 643 ...
##  $ LOCROTH                      : num  428 501 222 190 962 ...
##  $ TOTALEX                      : num  26135 10618 12460 7379 37999 ...
##  $ TCURSPN                      : num  6871 9142 11697 6468 29197 ...
##  $ TSALWAG                      : num  4021 5639 6706 3799 16988 ...
##  $ TEMPBEN                      : num  1496 1927 1789 977 6655 ...
##  $ TCURINS                      : num  3915 5729 6241 3755 18391 ...
##  $ TCURISA                      : num  2601 3974 4431 2515 11543 ...
##  $ TCURIBE                      : num  954 1221 1159 634 4455 ...
##  $ TCURSSV                      : num  2547 2722 4290 2054 9060 ...
##  $ TCURSPU                      : num  128 206 428 107 495 ...
##  $ TCURSST                      : num  167 237 533 229 813 ...
##  $ TCURSGE                      : num  369 523 239 208 1075 ...
##  $ TCURSSC                      : num  352 446 523 332 1454 ...
##  $ TCURSOT                      : num  1531 1310 2567 1178 5223 ...
##  $ TCURONO                      : num  409 691 1166 659 1746 ...
##  $ TCAPOUT                      : num  18168 561 473 333 5900 ...
##  $ TPAYOTH                      : num  224 186 286 347 947 ...
##  $ TINTRST                      : num  872 729 4 231 1955 ...
##  $ DEBTOUT                      : num  29340 24889 0 18095 56845 ...
##  $ LONGISS                      : num  0 0 0 0 0 ...
##  $ LONGRET                      : num  417 1239 455 767 3715 ...
##  $ PCTTOTA                      : num  100 100 100 100 100 100 100 100 100 100 ...
##  $ PCTFTOT                      : num  5.4 4.5 3.7 4.6 4.7 4.9 1.8 4.1 4.1 6.8 ...
##  $ PCTFCOM                      : num  2.1 1.4 1.1 2 1.3 0.8 0.4 0.9 1.3 2.3 ...
##  $ PCTSTOT                      : num  63.5 59.2 71.9 65.2 57.3 66.7 70.3 65.8 64.3 76.1 ...
##  $ PCTSFOR                      : num  44.4 45.4 54.8 53.2 44.1 52.7 50.2 41.4 51.7 58.6 ...
##  $ PCTLTOT                      : num  31.1 36.3 24.4 30.1 38 28.5 27.9 30.1 31.6 17.1 ...
##  $ PCTLTAX                      : num  3.6 28.7 14.5 20.9 16.1 18.5 18.1 18.5 3.5 9.1 ...
##  $ PCTLOTH                      : num  20.6 0.4 1.6 0.6 17.9 1.5 0.4 0.6 22 1 ...
##  $ PCTLCHA                      : num  1.6 3.1 6.5 6.3 1.6 5.2 6.5 4 2.5 4.8 ...
##  $ PPCSTOT                      : num  13215 12646 12006 11056 14162 ...
##  $ PPSALWG                      : num  7931 7976 7234 6820 8464 ...
##  $ PPEMPBE                      : num  2951 2726 1930 1754 3316 ...
##  $ PPITOTA                      : num  7722 8103 6732 6741 9163 ...
##  $ PPISALW                      : num  5130 5621 4780 4515 5751 ...
##  $ PPIEMBE                      : num  1882 1727 1250 1138 2220 ...
##  $ PPSTOTA                      : num  5024 3850 4628 3688 4514 ...
##  $ PPSPUPI                      : num  252 291 462 192 247 329 318 152 159 215 ...
##  $ PPSSTAF                      : num  329 335 575 411 405 473 724 333 605 440 ...
##  $ PPSGENA                      : num  728 740 258 373 536 290 268 185 457 271 ...
##  $ PPSSCHA                      : num  694 631 564 596 724 698 467 299 424 441 ...
##  $ Avg_Cmp                      : num  19.1 20.5 21.1 21 20.1 19.1 21.8 20.4 19.6 19.7 ...
##  $ TotalClasses                 : num  0 1 6 9 7 6 12 7 2 3 ...
##  $ NumCat                       : num  0 1 3 1 2 1 3 3 2 2 ...
##  $ geometry                     :sfc_MULTIPOLYGON of length 324; first list element: List of 1
##   ..$ :List of 1
##   .. ..$ : num [1:88, 1:2] -92.8 -92.8 -92.8 -92.8 -92.8 ...
##   ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
##  - attr(*, "sf_column")= chr "geometry"
##  - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
##   ..- attr(*, "names")= chr [1:94] "GEOID" "Distrct" "Dstrc_N" "mal_5t9" ...

After looking at the variables and their types, we noticed that we were dealing with predominantly numerical variables. GEOID, District, District.Nbr, CONUM, CSA, and CBSA are ID variables, so we will have to specify that in the recipe so that they won’t be included in the model.

We decided to drop the geometry column and convert the data to a regular data frame format for this part because it was not necessary for the models and including it led to issues running some functions.

mn <- mn %>%
  st_drop_geometry()

We plotted the distributions of our variables to see what kind of data transformation we would need to perform.

Below are sets of our predictors, first from the Annual Survey of School System Finances:

mn %>%
  select(-GEOID) %>%
  # variables from the Annual Survey of School System Finances are in all caps
  select(matches("^[A-Z]{4,}", ignore.case = FALSE)) %>%
  select(where(is.numeric)) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "values") %>%
  ggplot(aes(x = values)) +
  geom_histogram() +
  facet_wrap(vars(variable),
             scales = "free")

Now from the ACS and ACT data:

mn %>%
  # remove the ID variables
  select(-c("GEOID", "District", "District.Nbr")) %>%
  # these variables have at least one lower case character
  select(matches("[a-z]", ignore.case = FALSE)) %>%
  # filter for numerical variables
  select(where(is.numeric)) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "values") %>%
  ggplot(aes(x = values)) +
  geom_histogram() +
  facet_wrap(vars(variable),
             scales = "free")

A lot of the data are right-skewed, which makes sense as many variables are raw counts.

We also checked for missing data:

mn %>% 
  add_n_miss() %>% 
  count(n_miss_all)

We had one observation missing 65 variables, and this was the “Remainder of Minnesota” observation from the ACS. The NAs caused issues when we tried to fit a LASSO and random forest model, so we removed them beforehand.

mn <- mn %>%
  drop_na()

Model Recipe

From our data exploration, these were the things we had to do in the recipe:

  • Log-transform most of the variables (except for the ones that start with PCT from the School Survey as they’re percentages and PP as they’re spending per pupil and not right-skewed).
  • Use percentages for ACS variables.
  • Normalize all numerical variables.
  • Ignore PCTTOTA and LOCRPAR as all variables have the same value.
  • Make the ID variables evaluative (i.e. not included in modeling).

Split data

We split the data into a training and testing set and also created cross-validation folds for evaluation.

# split the data into a training and test set
mn_split <- initial_split(mn, prop = .75)
mn_training <- training(mn_split)
mn_testing <- testing(mn_split)
# create cross-validation folds
mn_cv <- vfold_cv(mn_training, v = 5)

Preprocess data

Here is the recipe for our models:

mn_recipe <- 
  recipe(TotalClasses ~ ., data = mn_training) %>%
  # ignore observations with missing data (necessary for LASSO mod)
  step_naomit(everything(), skip = TRUE) %>%
  # remove variables
  step_rm(
    # this one can be considered as response variable itself
    NumCat, 
    # all variables have the same value for these two
    PCTTOTA, LOCRPAR, 
    # raw counts from ACS
    matches("[a-z]", ignore.case = FALSE),
    -starts_with("perc"),
    -total_pop,
    -Avg_Cmp,
    -TotalClasses
    ) %>%
  # log-transform 
  step_log(
    # total population
    total_pop,
    # spending / revenue variables from the school survey
    ## ignore those that start with P since they're percentages / spending per student
    matches("^[A-OQ-Z]{4,}", ignore.case = FALSE), 
    ## ignore ID variables as well
    -GEOID, -CONUM, -CBSA,
    # some variables have 0s which will produce NaNs when log-transformed
    offset = 1) %>% 
  # make ID variables evaluative (not included in modeling)
  update_role(
    all_of(c("GEOID",
             "District",
             "District.Nbr",
             "CONUM",
             "CSA",
             "CBSA")),
    new_role = "evaluative") %>%
  # make integers numeric
  step_mutate_at(is.integer, fn = as.numeric) %>%
  # normalize numerical variables
  step_normalize(all_predictors())

Here is what the data looked like post-transformation:

mn_recipe %>%
  prep(mn_training) %>%
  juice()

Model Fitting

Regular linear regression

We first tested a regular linear regression model and looked at the table of coefficients. Since we had an overwhelming number of predictors, we assumed beforehand that this model would not perform well due to overfitting.

# define the model type
mn_linear_mod <-
  linear_reg() %>%
  set_engine("lm") %>%
  set_mode("regression")

# set up the workflow
mn_lm_wf <-
  workflow() %>%
  add_recipe(mn_recipe) %>%
  add_model(mn_linear_mod)

# fit the model
mn_lm_fit <-
  mn_lm_wf %>%
  fit(mn_training)

# display the results
mn_lm_fit %>% 
  pull_workflow_fit() %>% 
  tidy() %>% 
  mutate(across(where(is.numeric), ~round(.x,3))) 

LASSO

To deal with the massive number of predictors, we switched to LASSO to shrink coefficients to zero and thereby eliminate insignificant variables from the model.

# define the model type
mn_lasso_mod <-
  linear_reg(mixture = 1) %>%
  set_engine("glmnet") %>%
  set_args(penalty = tune()) %>%
  set_mode("regression")

# set up the workflow
mn_lasso_wf <-
  workflow() %>%
  add_recipe(mn_recipe) %>%
  add_model(mn_lasso_mod)

# set up penalty grid for tuning
penalty_grid <- grid_regular(penalty(),
                             levels = 20)

# tune the parameter
mn_lasso_tune <-
  mn_lasso_wf %>%
  tune_grid(
    resamples = mn_cv,
    grid = penalty_grid
  )

We chose the best parameter based on RMSE and finalized the workflow / model. We then looked at the variables that were retained by LASSO.

# show the best penalty parameter
mn_lasso_tune %>% 
  show_best(metric = "rmse")
# select best parameter by smallest rmse
(best_param <- mn_lasso_tune %>% 
    select_best(metric = "rmse"))
# finalize workflow
mn_lasso_final_wf <- mn_lasso_wf %>% 
  finalize_workflow(best_param)

# fit final model
mn_lasso_final_mod <-
  mn_lasso_final_wf %>%
  fit(data = mn_training)

# look at the table of coefficients
mn_lasso_final_mod %>% 
  pull_workflow_fit() %>% 
  tidy()  %>%
  # filter for predictors with non-zero coefficients 
  filter(estimate != 0)

Random forest

Our last model candidate was a random forest model.

# define the model type
mn_rf_mod <- 
  rand_forest(mtry = 23, # ~1/3 of predictors 
              min_n = 5, 
              trees = 200) %>% 
  set_mode("regression") %>% 
  set_engine("ranger")

# set up the workflow
mn_rf_wf <-
  workflow() %>%
  add_recipe(mn_recipe) %>%
  add_model(mn_rf_mod)

# fit the model
mn_rf_fit <- 
  mn_rf_wf %>% 
  fit(mn_training)

Model Evaluation and Comparison

# fit model with best tuning parameter(s) to training data and apply to test data
mn_lm_test <- 
  mn_lm_wf %>% 
  last_fit(mn_split)
mn_lasso_test <- 
  mn_lasso_final_wf %>% 
  last_fit(mn_split)
mn_rf_test <-
  mn_rf_wf %>%
  last_fit(mn_split)

Of our three models, the random forest performed best, followed by LASSO, and then regular linear regression.

# collect metrics for model applied to test data
mn_lm_test %>%
  collect_metrics
mn_lasso_test %>% 
  collect_metrics()
mn_rf_test %>% 
  collect_metrics()

Residuals

Since the RMSE from the regular lm model was rather high, we decided to only compare the LASSO and random forest models moving forward. We computed their overall performance metrics and looked at the residuals.

lasso_explain <- 
  explain_tidymodels(
    model = mn_lasso_final_mod,
    data = mn_training %>% select(-TotalClasses), 
    y = mn_training %>%  pull(TotalClasses),
    label = "lasso"
  )
## Preparation of a new explainer is initiated
##   -> model label       :  lasso 
##   -> data              :  235  rows  93  cols 
##   -> target variable   :  235  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.2 , task regression (  default  ) 
##   -> predicted values  :  numerical, min =  2.983908 , mean =  4.855319 , max =  43.34407  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -18.45726 , mean =  0.000000000000004801915 , max =  18.7106  
##   A new explainer has been created! 
rf_explain <- 
  explain_tidymodels(
    model = mn_rf_fit,
    data = mn_training %>% select(-TotalClasses), 
    y = mn_training %>%  pull(TotalClasses),
    label = "rf"
  )
## Preparation of a new explainer is initiated
##   -> model label       :  rf 
##   -> data              :  235  rows  93  cols 
##   -> target variable   :  235  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.2 , task regression (  default  ) 
##   -> predicted values  :  numerical, min =  0.64875 , mean =  4.921705 , max =  43.57892  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -8.43225 , mean =  -0.06638546 , max =  10.42108  
##   A new explainer has been created! 
# get overall performance metrics
lasso_mod_perf <- model_performance(lasso_explain)
rf_mod_perf <-  model_performance(rf_explain)

Here are tables of their performance metrics:

# LASSO
data.frame(lasso_mod_perf$measures)
# random forest
data.frame(rf_mod_perf$measures)

Here is the distribution of the residuals:

plot(lasso_mod_perf,
     rf_mod_perf, 
     geom = "boxplot")

Variable importance

In the end, the random forest showed to greatly outperform the LASSO. Our final step was to look at the variable importance plot from this final model.

set.seed(1) 
# create explainer
rf_explain <- 
  explain_tidymodels(
    model = mn_rf_fit,
    data = mn_training %>% select(-TotalClasses), 
    y = mn_training %>%  pull(TotalClasses),
    label = "rf"
  )
## Preparation of a new explainer is initiated
##   -> model label       :  rf 
##   -> data              :  235  rows  93  cols 
##   -> target variable   :  235  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.2 , task regression (  default  ) 
##   -> predicted values  :  numerical, min =  0.64875 , mean =  4.921705 , max =  43.57892  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -8.43225 , mean =  -0.06638546 , max =  10.42108  
##   A new explainer has been created! 
# compute variable importance
rf_var_imp <- model_parts(rf_explain)

Implications

One note that is critical to keep in mind is that correlation does not imply causation. Although this project looks at connections between various data sets and different variables, we are not suggesting that any of our predictors directly alters computer science course availability. It is possible that that is the case, given the work that we have done, but without an experiment or accounting for potential confounders (other variables that may affect the predictor and outcome variables), we cannot be certain about causation.

Previous work does exist about how disparities in education are related to many of the variables we displayed in our project, such as household income and race. For instance, it has been proven that ACT and standardized test scores show more about family wealth and privilege than actual intelligence or likelihood for success. Therefore, it is logical that the same districts that have high average ACT scores will have high median household incomes due to systemic inequality. Due to the fact that computer science is a newer field, less work has been done specifically about this subject. The rise in available literature on this subject in recent years has also been focused more on college and graduate school, with K-12 education receiving less attention.

That being said, there are many nuances to this issue of course availability and inequality that we could not address within the scope of our project. One variable we looked at was race, and while the connection between race and educational disparities has been studied, that can be difficult to see in some of our work. We hypothesize that there are a few reasons for this. First, Minnesota in general is largely populated by White people. Furthermore, the places with the most racial diversity (near the Twin Cities), are also places with considerable inequality. Without this information, it may seem as though there is correlation between greater diversity, higher median household income, and computer science course availability. However, we cannot make this claim without further investigating how the inequalities within each district play a role.

Along with that, the population size of the districts could affect the outcomes. Districts can encompass many schools, and it is possible that within a district there is variation in demographics and course availability. Future work might include investigating a smaller region to explore some of these nuances in order to better understand the connection between computer science course availability in K-12 education and our other variables.

LS0tCnRpdGxlOiAiRmluYWwgUHJvamVjdCAtIEJlaGluZCBUaGUgU2NlbmVzIgphdXRob3I6ICJBbmFlbCBLdXBlcndhanMgQ29oZW4sIENvbGxlZW4gTWlubmloYW4sIEhheWxleSBIYWRnZXMsIFRoeSBOZ3V5ZW4iCmRhdGU6ICI1LzA1LzIwMjEiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBkZl9wcmludDogcGFnZWQKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgojIEludHJvZHVjdGlvbgoKQ29tcHV0ZXIgc2NpZW5jZSBpcyBhIGZpZWxkIHRoYXQgaXMgZ3Jvd2luZyByYXBpZGx5IGluIHRoZSBVbml0ZWQgU3RhdGVzIGFuZCBhcm91bmQgdGhlIHdvcmxkIHRvZGF5LiBJbmR1c3RyeSBpcyBjb25zdGFudGx5IHJlbGVhc2luZyBhZHZhbmNlbWVudHMgaW4gY29tcHV0ZXIgc2NpZW5jZSBhbmQgdGVjaG5vbG9neSBpcyBiZWNvbWluZyBtb3JlIGluZ3JhaW5lZCBpbnRvIG91ciBkYWlseSBsaXZlcyB3aXRoIGVhY2ggcGFzc2luZyBkYXkuIFRoZXJlZm9yZSwgd2l0aCBhbiBpbmNyZWFzZSBpbiB0ZWNobm9sb2d5IHVzYWdlLCB0aGUgZGVtYW5kIGZvciBjb21wdXRlciBzY2llbnRpc3RzIGhhcyBpbmNyZWFzZWQgaW4gcG9wdWxhcml0eS4gRHVlIHRvIHRoaXMgZmFzdC1ncm93aW5nIGZpZWxkLCBlZHVjYXRpb25hbCBpbnN0aXR1dGlvbnMgYW5kIHN5c3RlbXMgYXJlIGluY3JlYXNpbmcgdGhlIGFtb3VudCBvZiBjb21wdXRlciBzY2llbmNlIGNvdXJzZXMgb2ZmZXJlZCBpbiBvcmRlciB0byB0cmFpbiBtb3JlIGZ1dHVyZSBjb21wdXRlciBzY2llbnRpc3RzLgoKVGhlc2UgaW5jcmVhc2VzIHN0YXJ0ZWQgYXQgdGhlIGNvbGxlZ2UgbGV2ZWwsIHdoZXJlIG1ham9yaW5nIGluIGNvbXB1dGVyIHNjaWVuY2UgaXMgbm93IGJlY29taW5nIGEgd2lkZWx5IGF2YWlsYWJsZSBvcHRpb24uIEF0IE1hY2FsZXN0ZXIgQ29sbGVnZSwgaXQgaXMgb25lIG9mIHRoZSBsYXJnZXN0IGRlcGFydG1lbnRzIGZvciBib3RoIHN0dWRlbnRzIGFuZCBmYWN1bHR5LiBXaGlsZSB0aGUgYXZhaWxhYmlsaXR5IG9mIGNvdXJzZXMgYXQgdGhlIGNvbGxlZ2UgbGV2ZWwgaXMgYSBnb29kIHN0YXJ0LCB0aGVyZSBpcyBhIGJpZyBwdXNoIHRvIGhhdmUgY29tcHV0ZXIgc2NpZW5jZSBjb3Vyc2VzIG9mZmVyZWQgaW4gSy0xMiBlZHVjYXRpb24uIE9mZmVyaW5nIGNvbXB1dGVyIHNjaWVuY2UgY291cnNlcyBpbiBlbGVtZW50YXJ5IGFuZCBzZWNvbmRhcnkgc2Nob29scyBwcm92aWRlcyBhbiBvcHBvcnR1bml0eSBmb3Iga2lkcyB0byBleHBvc2UgdGhlbXNlbHZlcyB0byBjb2RpbmcsIHBvdGVudGlhbGx5IGxlYWRpbmcgdG8geW91bmdlciBzdHVkZW50cyBkaXNjb3ZlcmluZyBuZXcgaW50ZXJlc3RzIGFuZCBhY3RpdmVseSBlbmdhZ2luZyB3aXRoIGNvbXB1dGVyIHNjaWVuY2UgZWFybGllci4gT2Z0ZW50aW1lcywgYmVpbmcgZXhwb3NlZCB0byBjb21wdXRlciBzY2llbmNlIGF0IGEgeW91bmdlciBhZ2UgY2FuIG1ha2Ugc3R1ZGVudHMgbW9yZSBjb21mb3J0YWJsZSB3aXRoIHRoZSBtYXRlcmlhbCBhbmQgdGhlIGZpZWxkIGxhdGVyIG9uLCB3aGljaCBjYW4gY29udHJpYnV0ZSB0byBhIG1vcmUgZW1wb3dlcmVkIGFuZCBkaXZlcnNlIHNldCBvZiBzdHVkZW50cyBlbnRlcmluZyB0aGUgd29ya2ZvcmNlIG9yIGhpZ2hlciBlZHVjYXRpb24uIEdpdmVuIHRoZSBpbXBvcnRhbmNlIG9mIGhhdmluZyBjb21wdXRlciBzY2llbmNlIGNvdXJzZXMgYXZhaWxhYmxlIGluIEstMTIgZWR1Y2F0aW9uLCB3ZSBkZWNpZGVkIHRvIGludmVzdGlnYXRlIHRoZSBhdmFpbGFiaWxpdHkgb2YgY29tcHV0ZXIgc2NpZW5jZSBjb3Vyc2VzIGluIEstMTIgc2Nob29sIGRpc3RyaWN0cyBpbiBNaW5uZXNvdGEuIEluIHRoaXMgcHJvamVjdCwgd2UgZXhwbG9yZSB0aGUgY29ubmVjdGlvbiBiZXR3ZWVuIGEgdmFyaWV0eSBvZiBkYXRhIHNldHMgcmVsYXRlZCB0byB0aGlzIHRvcGljLCBpbmNsdWRpbmcgSy0xMiBjb21wdXRlciBzY2llbmNlIGNvdXJzZSBhdmFpbGFiaWxpdHkgaW4gTWlubmVzb3RhLCBkZW1vZ3JhcGhpYyBpbmZvcm1hdGlvbiBmcm9tIHRoZSBVLlMuIGNlbnN1cywgQUNUIHNjb3JlcywgYW5kIHNjaG9vbCBkaXN0cmljdCBmaW5hbmNpYWwgaW5mb3JtYXRpb24uCgpUbyBzZWUgb3VyIGZpbmFsIHByb2R1Y3QsIHZpc2l0OiBodHRwczovL2FkdmFuY2VkLWRhdGEtc2NpZW5jZS1maW5hbC1wcm9qZWN0Lm5ldGxpZnkuYXBwCgoKIyBBYm91dCB0aGUgRGF0YSBTZXRzCgoqKk1pbm5lc290YSBDb21tb24gQ291cnNlIENhdGFsb2d1ZSoqCgpUaGlzIGRhdGEgc2V0IGlzIHByb3ZpZGVkIGJ5IHRoZSBNaW5uZXNvdGEgU3RhdGUgRGVwYXJ0bWVudCBvZiBFZHVjYXRpb24gYW5kIGlzIGF2YWlsYWJsZSB0byB0aGUgcHVibGljLiBUaGUgTWlubmVzb3RhIENvbW1vbiBDb3Vyc2UgQ2F0YWxvZ3VlIChNQ0NDKSBwcmVzZW50cyBpbmZvcm1hdGlvbiBhYm91dCBhbGwgdGhlIGNvdXJzZXMgdGhhdCBhcmUgb2ZmZXJlZCBhY3Jvc3Mgc2Nob29sIGRpc3RyaWN0cyBpbiBNaW5uZXNvdGEuIEZvciBvdXIgcHVycG9zZXMsIHdlIGFyZSBsb29raW5nIGF0IHN1YmplY3QgYXJlYSAxMCwgY29tcHV0ZXIgYW5kIGluZm9ybWF0aW9uIHNjaWVuY2VzIChLLTEyKSwgYW5kIGFsbCBjYXRlZ29yaWVzIGFuZCBjbGFzc2lmaWNhdGlvbnMuIEZvciBlYWNoIGNvdXJzZSBjbGFzc2lmaWNhdGlvbiwgdGhlcmUgaXMgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGRpc3RyaWN0IHdoZXJlIGl0IGlzIHRhdWdodCwgdGhlIHJlZ2lvbiBvZiB0aGUgc3RhdGUsIHRoZSBsb2NhbCBjb3Vyc2UgdGl0bGUsIHdoZXRoZXIgaXQgaXMgYW4gQVAgb3IgSUIgY291cnNlLCBpZiBpdCBmdWxmaWxscyBhIGdyYWR1YXRpb24gcmVxdWlyZW1lbnQsIGFtb25nIG90aGVycy4gV2Ugam9pbmVkIHRoaXMgZGF0YSBzZXQgd2l0aCBpbmZvcm1hdGlvbiBhYm91dCB3aGF0IGNhdGVnb3J5IHRoZSBjb3Vyc2UgY2xhc3NpZmljYXRpb24gZmFsbHMgaW50bywgc3VjaCBhcyBBIC0gY29tcHV0ZXIgbGl0ZXJhY3ksIEIgLSBtYW5hZ2VtZW50IGluZm9ybWF0aW9uIHN5c3RlbXMsIG9yIEMgLSBuZXR3b3JrIHN5c3RlbXMuIFRoZSBmaW5hbCBkYXRhIHNldCBoYXMgMSw2MDIgY29tcHV0ZXIgYW5kIGluZm9ybWF0aW9uIGNsYXNzZXMgdGF1Z2h0IGFjcm9zcyB0aGUgc3RhdGUgaW4gSy0xMiBlZHVjYXRpb24uCgpMaW5rOiBodHRwczovL3B1YmxpYy5lZHVjYXRpb24ubW4uZ292L01ERUFuYWx5dGljcy9EYXRhVG9waWMuanNwP1RPUElDSUQ9ODQKCgoqKkFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkqKgoKV2UgYWNjZXNzZWQgdGhlIDIwMTkgQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSAoQUNTKSBmcm9tIHRoZSB0aWR5Y2Vuc3VzIHBhY2thZ2UgaW4gUi4gVGhlIEFDUyBpcyBhbiBhbm51YWwgZGVtb2dyYXBoaWMgc3VydmV5IGZyb20gdGhlIFUuUy4gQ2Vuc3VzIEJ1cmVhdS4gV2UgdXNlZCB0aGlzIGRhdGEgc2V0IHRvIGZpbmQgYnktZGlzdHJpY3QgaW5mb3JtYXRpb24gYWJvdXQgcmFjZSwgc29jaW9lY29ub21pYyBzdGF0dXMsIGxpdmluZyBlbnZpcm9ubWVudHMsIGFuZCBvdGhlciByZWxldmFudCB2YXJpYWJsZXMgZm9yIG91ciBhbmFseXNlcyBhbmQgdmlzdWFsaXphdGlvbnMuIFdlIGh5cG90aGVzaXplZCB0aGF0IGNvbXB1dGVyIHNjaWVuY2UgY291cnNlIGF2YWlsYWJpbGl0eSB3b3VsZCBiZSBjb25uZWN0ZWQgdG8gb25lIG9yIG1vcmUgb2YgdGhlc2UgdmFyaWFibGVzLCBkdWUgdG8gdGhlIHRyZW5kIHdoZXJlIG1vcmUgY29tcHV0ZXIgc2NpZW5jZSBjb3Vyc2VzIGFyZSBvZmZlcmVkIGluIHdlYWx0aGllciBhcmVhcyBhY3Jvc3MgdGhlIGNvdW50cnkgdGhhdCBoYXZlIG1vcmUgcmVzb3VyY2VzLiBXZSBhcmUgdXNpbmcgdGhpcyBkYXRhIHNldCB0byBmdXJ0aGVyIGludmVzdGlnYXRlIHRoYXQgcG9zc2liaWxpdHkuCgoKKipBQ1QgRGF0YSoqCgpXZSByZXRyaWV2ZWQgdGhpcyBkYXRhIGZyb20gdGhlIE1pbm5lc290YSBTdGF0ZSBEZXBhcnRtZW50IG9mIEVkdWNhdGlvbiwgd2hpY2ggZ2l2ZXMgdXMgYXZlcmFnZSBjb21wb3NpdGUgQUNUIHNjb3JlcyBmb3IgZWFjaCBzY2hvb2wgZGlzdHJpY3QgaW4gTWlubmVzb3RhIGZvciBhIHJhbmdlIG9mIHllYXJzLiBJdCBoYXMgYmVlbiBwcm92ZW4gdGhhdCBoaWdoZXIgQUNUIHNjb3JlcyBhcmUgY29ubmVjdGVkIHRvIHdlYWx0aCwgd2hpY2ggd291bGQgbGVhZCB1cyB0byBleHBlY3QgdGhhdCBkaXN0cmljdHMgd2l0aCBtb3JlIHJlc291cmNlcyB3b3VsZCBoYXZlIGhpZ2hlciBhdmVyYWdlIEFDVCBzY29yZXMuIEFzIGFmb3JlbWVudGlvbmVkLCB3ZSBwcmVkaWN0IHRoYXQgY29tcHV0ZXIgc2NpZW5jZSBjb3Vyc2UgYXZhaWxhYmlsaXR5IGlzIGdyZWF0ZXIgaW4gYXJlYXMgd2l0aCBtb3JlIHdlYWx0aCBhbmQgcmVzb3VyY2VzLCB0aGVyZWZvcmUgd2UgdGhvdWdodCB0aGVzZSBzY29yZXMgbWF5IGJlIGFub3RoZXIgaW50ZXJlc3RpbmcgdmFyaWFibGUgaW4gcHJlZGljdGluZyBvciB2aXN1YWxpemluZyBhdmFpbGFiaWxpdHkgb2YgY29tcHV0ZXIgc2NpZW5jZSBjb3Vyc2VzIGJ5IGRpc3RyaWN0LiBJbiB0aGUgc2FtZSB3YXkgd2UgZXhwZWN0IGNlcnRhaW4gZGVtb2dyYXBoaWMgaW5mb3JtYXRpb24gdG8gYmUgY29ycmVsYXRlZCB3aXRoIGNvbXB1dGVyIHNjaWVuY2UgYXZhaWxhYmlsaXR5LCB3ZSBhcmUgaG9waW5nIHRvIHVzZSBhdmVyYWdlIEFDVCBzY29yZSBpbmZvcm1hdGlvbiB0byBwcmVzZW50IGFub3RoZXIgbWV0aG9kIG9mIHNob3dpbmcgdGhlIGNvbm5lY3Rpb24uCgpMaW5rOiBodHRwczovL3B1YmxpYy5lZHVjYXRpb24ubW4uZ292L01ERUFuYWx5dGljcy9EYXRhVG9waWMuanNwP1RPUElDSUQ9ODcKCgoqKkFubnVhbCBTdXJ2ZXkgb2YgU2Nob29sIFN5c3RlbSBGaW5hbmNlcyoqCgpBbm90aGVyIGltcG9ydGFudCBzZXQgb2YgZGF0YSB0aGF0IHdlIHVzZWQgZnJvbSB0aGUgVS5TLiBDZW5zdXMgQnVyZWF1IGlzIHRoZSBBbm51YWwgU3VydmV5IG9mIFNjaG9vbCBTeXN0ZW0gRmluYW5jZXMgKEFTU1NGKS4gVGhpcyBkYXRhIGNvbnRhaW5zIGluZm9ybWF0aW9uIGFib3V0IDIwMTggZmluYW5jaWFsIGFjdGl2aXR5IG9mIHB1YmxpYyBlbGVtZW50YXJ5IGFuZCBzZWNvbmRhcnkgc2Nob29sIHN5c3RlbXMgZm9yIGFsbCBzdGF0ZXMgYWNyb3NzIHRoZSBjb3VudHJ5LiBJbiBwYXJ0aWN1bGFyLCB3ZSB3ZXJlIGludGVyZXN0ZWQgaW4gZGlzY292ZXJpbmcgaWYgZnVuZGluZywgcmV2ZW51ZSwgb3Igc3BlbmRpbmcgZm9yIGEgZGlzdHJpY3Qgd2FzIGNvcnJlbGF0ZWQgdG8gYXZhaWxhYmlsaXR5IG9mIGNvbXB1dGVyIHNjaWVuY2UgY291cnNlcy4gRHVlIHRvIHRoZSBmYWN0IHRoYXQgcHVibGljIHNjaG9vbCBmdW5kaW5nIG9mdGVuIGNvbWVzIGZyb20gaW5jb21lIHRheGVzLCB3ZWFsdGhpZXIgYXJlYXMgcHJvdmlkZSBtb3JlIGZ1bmRpbmcgZm9yIHRoZWlyIGxvY2FsIHNjaG9vbHMuCgpMaW5rOiBodHRwczovL3d3dy5jZW5zdXMuZ292L3Byb2dyYW1zLXN1cnZleXMvc2Nob29sLWZpbmFuY2VzLmh0bWwKCgoqKk1pbm5lc290YSBTY2hvb2wgRGlzdHJpY3RzKioKClRoaXMgZGF0YSBzZXQgaW5jbHVkZXMgdGhlIG5hbWUgb2YgYWxsIHNjaG9vbCBkaXN0cmljdHMgaW4gTWlubmVzb3RhIGFzIHdlbGwgYXMgdGhlaXIgZGlzdHJpY3QgbnVtYmVycywgd2hpY2ggd2FzIGNyZWF0ZWQgaW4gMjAxNS4gVGhpcyBkYXRhIGNhbWUgZnJvbSB0aGUgTWlubmVzb3RhIFN0YXRlIERlcGFydG1lbnQgb2YgSHVtYW4gU2VydmljZXMuIFdlIGpvaW5lZCB0aGUgZGlzdHJpY3QgbnVtYmVycyBmcm9tIHRoaXMgZGF0YSBzZXQgdG8gdGhlIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkgZGF0YSwgd2hpY2ggb25seSBoYWQgdGhlIGRpc3RyaWN0IG5hbWVzLiBBY2Nlc3NpbmcgZGlzdHJpY3QgbnVtYmVycyBhbmQgbmFtZXMgd2FzIGludGVncmFsIGluIGNvbm5lY3Rpbmcgb3VyIHZhcmlvdXMgZGF0YSBzZXRzIGJlY2F1c2UgdGhlIGNvbW1vbiBwaWVjZSBvZiBpbmZvcm1hdGlvbiBiZXR3ZWVuIG91ciBpZGVhcyBpcyBkaXN0cmljdCBpbmZvcm1hdGlvbi4KCkxpbms6IGh0dHBzOi8vd3d3LmRocy5zdGF0ZS5tbi51cy9tYWluL2dyb3Vwcy9jb3VudHlfYWNjZXNzL2RvY3VtZW50cy9wdWIvZGhzMTZfMTkzNTkxLnBkZgoKCiMgSW1wb3J0YW50IFBhY2thZ2VzIGFuZCBEYXRhIFNldHMKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQpvcHRpb25zKHNjaXBlbiA9IDk5OSkKYGBgCgoqKkxvYWQgUGFja2FnZXMqKgoKYGBge3J9CmxpYnJhcnkodGlkeWNlbnN1cykgIyBmb3IgZ2V0dGluZyB0aGUgY2Vuc3VzIGRhdGEKbGlicmFyeSh0aWR5dmVyc2UpICMgZm9yIGRhdGEgY2xlYW5pbmcgYW5kIHZpc3VhbGl6YXRpb24KbGlicmFyeShzZikgIyBmb3IgbWFwcGluZwpsaWJyYXJ5KHRpZHltb2RlbHMpCmxpYnJhcnkobmFuaWFyKQpsaWJyYXJ5KERBTEVYKQpsaWJyYXJ5KERBTEVYdHJhKQpsaWJyYXJ5KHZpcCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkocGxvdGx5KQpgYGAKCioqTG9hZCBEYXRhIFNldHMqKgoKYGBge3J9CmFjdCA8LSByZWFkX2NzdigiYWN0X2RhdGEuY3N2IikgIyBBQ1QKZGlzdHJpY3RzIDwtIHJlYWRfY3N2KCJkaXN0cmljdHMuY3N2IikgIyBNTiBTY2hvb2wgRGlzdHJpY3RzCmNvdXJzZV9jYXRhbG9nIDwtIHJlYWRfY3N2KCJjb3Vyc2VfY2F0YWxvZy5jc3YiKSAjIE1DQ0MKc3MgPC0gcmVhZHhsOjpyZWFkX2V4Y2VsKCJlbHNlYzE4dC54bHMiKSAjIEFTU1NGCmBgYAoKCiMgRGF0YSBDbGVhbmluZwoKKipNQ0NDIERhdGEqKgoKVGhlIGRhdGEgZnJvbSB0aGUgRGVwYXJ0bWVudCBvZiBFZHVjYXRpb24gd2FzIG5lYXRseSBzcGxpdCBpbnRvIHNwcmVhZHNoZWV0cyBiYXNlZCBvbiBjb3Vyc2UgY2xhc3NpZmljYXRpb25zLiBJbiBvcmRlciB0byB1c2UgdGhpcyBkYXRhIG1vcmUgZWFzaWx5LCB3ZSBtYW51YWxseSBjb21iaW5lZCBhbGwgb2YgdGhlIHNwcmVhZHNoZWV0cyBpbnRvIG9uZSBkYXRhIHNldCB3aXRoIGFsbCBvZiB0aGUgY291cnNlcyBvZmZlcmVkLiBXZSBoYWQgdG8gbWFudWFsbHkgYWRkIGluIHRoZSBjb2x1bW5zIGZvciBjb3Vyc2UgY2xhc3NpZmljYXRpb24gYW5kIGNhdGVnb3J5IGluIG9yZGVyIHRvIGNyZWF0ZSBvbmUgZGF0YSBzZXQuIEZ1cnRoZXJtb3JlLCBpbiBvcmRlciB0byBjb25uZWN0IHRvIHRoZSBvdGhlciBkYXRhIHNldHMsIHdlIG5lZWRlZCB0byBoYXZlIHRoZSBkaXN0cmljdCBudW1iZXIgYXMgaXRzIG93biB2YXJpYWJsZS4gSW4gdGhpcyBkYXRhIHNldCwgdGhlIGRpc3RyaWN0IG5hbWUgYW5kIG51bWJlciB3ZXJlIGNvbWJpbmVkIGFzIG9uZSB2YXJpYWJsZSwgc28gd2UgdXNlZCByZWd1bGFyIGV4cHJlc3Npb25zIHRvIHNwbGl0IHRoZSBuYW1lIGFuZCBudW1iZXIuIFRoaXMgd2FzIGZhaXJseSBzaW1wbGUsIGR1ZSB0byB0aGUgZGlzdHJpY3QgdmFyaWFibGUgY29uc2lzdGVudGx5IGhhdmluZyB0aGUgbnVtYmVyIGFzIHRoZSBsYXN0IDcgZGlnaXRzLgoKYGBge3J9CmNvdXJzZV9jYXRhbG9nX3ByZWRpY3RvcnMgPC0KY291cnNlX2NhdGFsb2cgJT4lCiAgbXV0YXRlKERpc3ROdW0gPSBhcy5pbnRlZ2VyKHN0cl9zdWIoYERpc3RyaWN0YCwgLTcsLTQpKSkgJT4lCiAgZ3JvdXBfYnkoRGlzdE51bSkgJT4lCiAgc3VtbWFyaXNlKFRvdGFsQ2xhc3NlcyA9IHN1bShuKCkpLAogICAgICAgICAgICBOdW1DYXQgPSBsZW5ndGgodW5pcXVlKENhdGVnb3J5KSkpCmBgYAoKCioqQUNTIERhdGEqKgoKVGhlIEFDUyBkYXRhIHdhcyBhbHJlYWR5IGluIGEgdGlkeSBmb3JtYXQsIHdoaWNoIHdhcyBjb252ZW5pZW50IGZvciBvdXIgcHVycG9zZXMuIFdlIGZvY3VzZWQgb24gbmFycm93aW5nIGRvd24gdmFyaWFibGVzIHRvIHRoZSBvbmVzIHRoYXQgd2VyZSBpbXBvcnRhbnQgdG8gb3VyIHByb2plY3QgYW5kIHJlbmFtaW5nIHRoZW0gc28gdGhhdCB3ZSBjb3VsZCBlYXNpbHkgdW5kZXJzdGFuZCB0aGUgbWVhbmluZyBvZiB0aGUgdmFyaWFibGUuIE1hbnkgb2YgdGhlIGRpc3RyaWN0cyBoYWQgdGhlIHdvcmQgJ01pbm5lc290YScgaW4gaXQsIHNvIHdlIHJlbW92ZWQgdGhhdCBiZWNhdXNlIGFsbCBvZiBvdXIgZGF0YSBpcyBhYm91dCBNaW5uZXNvdGEuIFdlIGFsc28gY3JlYXRlZCBzb21lIHBlcmNlbnRhZ2UgdmFyaWFibGVzIGJlY2F1c2UgdGhleSB3b3VsZCBiZSBtb3JlIG1lYW5pbmdmdWwgZm9yIG91ciBleHBsb3JhdGlvbiB0aGFuIHRoZSByYXcgbnVtYmVycyB0aGF0IHRpZHljZW5zdXMgcHJvdmlkZWQgdXMgd2l0aC4KCmBgYHtyfQojIGNlbnN1c19hcGlfa2V5KCJrZXkiKQoKbW5fMjAxOV9jZW5zdXMgPC0gZ2V0X2FjcyhzdGF0ZSA9ICJNTiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgZ2VvZ3JhcGh5ID0gInNjaG9vbCBkaXN0cmljdCAodW5pZmllZCkiLAogICAgICAgICAgICAgICAgICAgICAgICAgdmFyaWFibGVzID0gYygiQjAxMDAzXzAwMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCMDEwMDFfMDAxIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkIwMTAwMV8wMDQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQjAxMDAxXzAwNSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCMDEwMDFfMDA2IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkIwMjAwMV8wMDEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQjAyMDAxXzAwMiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCMDIwMDFfMDAzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkIwMjAwMV8wMDUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQjAyMDAxXzAwNiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCMDIwMDFfMDA4IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkIxOTAxM18wMDEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQjA5MDEwXzAwMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCMDkwMTBfMDAyIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCMjUwMzFfMDAxIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkIyNTAyN18wMDEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQjI1MDI3XzAwMiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCMjgwMDJfMDAxIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkIyODAwMl8wMDIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21ldHJ5ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgIHllYXIgPSAyMDE5KQoKbW5fMjAxOV9jZW5zdXNfbm9nZW9tIDwtIG1uXzIwMTlfY2Vuc3VzICU+JQogIHNlbGVjdCgtYG1vZWApICU+JSAjZ2V0IHJpZCBvZiBtb2UgdmFyaWFibGUobm90IHN1cmUgd2hhdCBpdCBpcykKICBzcHJlYWQodmFyaWFibGUsIGVzdGltYXRlKSAlPiUgI2dpdmUgZWFjaCB2YXJpYWJsZSBhIGNvbHVtbgogIHJlbmFtZSh0b3RhbF9wb3AgPSBCMDEwMDNfMDAxLAogICAgICAgICBtYWxlXzV0bzkgPSBCMDEwMDFfMDA0LAogICAgICAgICBtYWxlXzEwdG8xNCA9IEIwMTAwMV8wMDUsCiAgICAgICAgIG1hbGVfMTV0bzE3ID0gQjAxMDAxXzAwNiwKICAgICAgICAgcmFjZV93aGl0ZV9vbmx5ID0gQjAyMDAxXzAwMiwKICAgICAgICAgcmFjZV9CbGFja19vbmx5ID0gQjAyMDAxXzAwMywKICAgICAgICAgcmFjZV9Bc2lhbl9vbmx5ID0gQjAyMDAxXzAwNSwKICAgICAgICAgcmFjZV9wYWNpZmljX2lzbGFuZGVyX29ubHkgPSBCMDIwMDFfMDA2LAogICAgICAgICBhdF9sZWFzdF90d29fcmFjZXMgPSBCMDIwMDFfMDA4LAogICAgICAgICBtZWRfaG91c2Vob2xkX2luY18xMm1vID0gQjE5MDEzXzAwMSwKICAgICAgICAgU1NJX3B1YmFzc2lzdF9mb29kc3RhbXBzID0gQjA5MDEwXzAwMiwKICAgICAgICAgbWVkX2dyb3NzX3JlbnQgPSBCMjUwMzFfMDAxLAogICAgICAgICBob3VzZV91bml0c193X21vcnRnYWdlID0gQjI1MDI3XzAwMiwKICAgICAgICAgaW50ZXJuZXRfc3Vic2NyaXBfaW5faG91c2UgPSBCMjgwMDJfMDAyCiAgICAgICAgICkgJT4lICNyZW5hbWUgdmFyaWFibGVzCiAgbXV0YXRlKERpc3RyaWN0ID0gc3RyX2V4dHJhY3QoYE5BTUVgLCAiW14sXSsiKSkgJT4lICNkZWxldGUgIiwgTWlubmVzb3RhIiBhZnRlciBkaXN0cmljdCBuYW1lCiAgc2VsZWN0KC1OQU1FKSAlPiUgI3JlbW92ZSBgTkFNRWAgY29sdW1uCiAgbXV0YXRlKHBlcmNfbWFsZV81dG85ID0gbWFsZV81dG85L0IwMTAwMV8wMDEsCiAgICAgICAgIHBlcmNfbWFsZV8xMHRvMTQgPSBtYWxlXzEwdG8xNC9CMDEwMDFfMDAxLAogICAgICAgICBwZXJjX21hbGVfMTV0bzE3ID0gbWFsZV8xNXRvMTcvQjAxMDAxXzAwMSwKICAgICAgICAgcGVyY193aGl0ZV9vbmx5ID0gcmFjZV93aGl0ZV9vbmx5L0IwMjAwMV8wMDEsCiAgICAgICAgIHBlcmNfYmxhY2tfb25seSA9IHJhY2VfQmxhY2tfb25seS9CMDIwMDFfMDAxLAogICAgICAgICBwZXJjX2FzaWFuX29ubHkgPSByYWNlX0FzaWFuX29ubHkvQjAyMDAxXzAwMSwKICAgICAgICAgcGVyY19wYWNpZmljX2lzbGFuZGVyX29ubHkgPSByYWNlX3BhY2lmaWNfaXNsYW5kZXJfb25seS9CMDIwMDFfMDAxLAogICAgICAgICBwZXJjX2F0X2xlYXN0X3R3b19yYWNlcyA9IGF0X2xlYXN0X3R3b19yYWNlcy9CMDIwMDFfMDAxLAogICAgICAgICBwZXJjX1NTSV9wdWJhc3Npc3RfZm9vZHN0YW1wcyA9IFNTSV9wdWJhc3Npc3RfZm9vZHN0YW1wcy9CMDkwMTBfMDAxLAogICAgICAgICBwZXJjX2hvdXNlX3VuaXRzX3dfbW9ydGdhZ2UgPSBob3VzZV91bml0c193X21vcnRnYWdlL0IyNTAyN18wMDEsCiAgICAgICAgIHBlcmNfaW50ZXJuZXRfc3Vic2NyaXB0aW9uID0gaW50ZXJuZXRfc3Vic2NyaXBfaW5faG91c2UvQjI4MDAyXzAwMQogICAgICAgICApICU+JSAjY3JlYXRlIHBlcmNlbnRhZ2UgdmFyaWFibGVzCiAgc2VsZWN0KC1CMDEwMDFfMDAxLC1CMDIwMDFfMDAxLCAtQjA5MDEwXzAwMSwgLUIyNTAyN18wMDEsIC1CMjgwMDJfMDAxICkgI2Rlc2VsZWN0IHZhcmlhYmxlcyB1c2VkIHRvIGNhbGN1bGF0ZSAlIGJ1dCBhcmVuJ3QgaW1wb3J0YW50IGZvciBvdXIgdmlzdWFsaXphdGlvbnMKYGBgCgoKKipBQ1QgRGF0YSoqCgpUaGlzIGRhdGEgc2V0IGluY2x1ZGVkIGEgcmFuZ2Ugb2YgeWVhcnMsIHNvIHdlIGZpbHRlcmVkIHRoZSBncmFkdWF0aW9uIHllYXIgdG8gb3VyIHllYXIgb2YgaW50ZXJlc3Q6IDIwMTguIFRoaXMgZGF0YSBzZXQgYWxzbyBvcmlnaW5hbGx5IGhhZCBleHRyYSB2YXJpYWJsZXMgdGhhdCB3ZXJlIGlycmVsZXZhbnQgdG8gb3VyIGludGVyZXN0cywgc28gb3VyIGNsZWFuaW5nIHByb2Nlc3MgaW52b2x2ZWQgcmVtb3ZpbmcgcHJlZGljdG9ycyBzbyB0aGF0IHdlIHdlcmUgb25seSBsZWZ0IHdpdGggZGlzdHJpY3QgbmFtZSBhbmQgYXZlcmFnZSBjb21wb3NpdGUgQUNUIHNjb3JlLiBUaGUgZGlzdHJpY3QgbmFtZSBpbmNsdWRlZCB0aGUgZGlzdHJpY3QgbnVtYmVyLCBzbyBpbiBvcmRlciB0byBjcmVhdGUgdHdvIHNlcGFyYXRlIHZhbHVlcyB3ZSB1c2VkIHJlZ3VsYXIgZXhwcmVzc2lvbnMuIFRoZSBkaXN0cmljdCBudW1iZXIgd2FzIGNvbnNpc3RlbnRseSBsb2NhdGVkIGF0IHRoZSBzYW1lIHNwb3QgaW4gdGhlIG5hbWUsIHNvIHNwbGl0dGluZyB0aGVtIHdhcyByZWxhdGl2ZWx5IHN0cmFpZ2h0Zm9yd2FyZC4KCmBgYHtyfQphY3RfY2xlYW4gPC0KYWN0ICU+JQogIGZpbHRlcihgR3JhZCBZZWFyYCA9PSAyMDE4LAogICAgICAgICBgQW5hbHlzaXMgTGV2ZWxgID09ICJEaXN0cmljdCIsCiAgICAgICAgIGBEaXN0cmljdCBOYW1lYCAhPSAiTUlOTkVTT1RBIERFUFQgT0YgRURVQ0FUSU9OIiwKICAgICAgICAgYEF2ZyBDb21wYCAhPSAiLiIpICU+JQogIHNlbGVjdChgRGlzdHJpY3QgTmFtZWAsIGBBdmcgQ29tcGApICU+JQogIG11dGF0ZShgRGlzdCBOdW1gID0gYXMuaW50ZWdlcihzdHJfZXh0cmFjdF9hbGwoYERpc3RyaWN0IE5hbWVgLCAiWzpkaWdpdDpdKyIpKSkgJT4lCiAgbXV0YXRlKGBEaXN0IE51bWA9IHJlcGxhY2UoYERpc3QgTnVtYCwgYERpc3RyaWN0IE5hbWVgPT0iR1JBTkFEQS1IVU5UTEVZLUVBU1QgQ0hBSU4gU0QiLCAyNTM2KSwKICAgICAgICAgYERpc3QgTnVtYD0gcmVwbGFjZShgRGlzdCBOdW1gLCBgRGlzdHJpY3QgTmFtZWA9PSJBTEJFUlQgTEVBIEFSRUEgU0NIT09MUyIsIDI0MSksCiAgICAgICAgIGBEaXN0IE51bWA9IHJlcGxhY2UoYERpc3QgTnVtYCwgYERpc3RyaWN0IE5hbWVgPT0iQVRXQVRFUi1DT1NNT1MtR1JPVkUgQ0lUWSBTRCIsIDIzOTYpLAogICAgICAgICBgRGlzdCBOdW1gPSByZXBsYWNlKGBEaXN0IE51bWAsIGBEaXN0cmljdCBOYW1lYD09IkJPTEQgU0NIT09MIERJU1RSSUNUIiwgMjUzNCksCiAgICAgICAgIGBEaXN0IE51bWA9IHJlcGxhY2UoYERpc3QgTnVtYCwgYERpc3RyaWN0IE5hbWVgPT0iQlVGRkFMTyBIQU5PVkVSIE1PTlRST1NFIFNEIiwgMDg3NyksCiAgICAgICAgIGBEaXN0IE51bWA9IHJlcGxhY2UoYERpc3QgTnVtYCwgYERpc3RyaWN0IE5hbWVgPT0iQlVGRkFMTyBMQUtFLUhFQ1RPUi1TVEVXQVJUIFNEIiwgMjE1OSksCiAgICAgICAgIGBEaXN0IE51bWA9IHJlcGxhY2UoYERpc3QgTnVtYCwgYERpc3RyaWN0IE5hbWVgPT0iRUFTVEVSTiBDQVJWRVIgQ08gU0NIT09MUyIsIDExMiksCiAgICAgICAgIGBEaXN0IE51bWA9IHJlcGxhY2UoYERpc3QgTnVtYCwgYERpc3RyaWN0IE5hbWVgPT0iTUFOS0FUTyBBUkVBIFBVQkxJQyBTQ0hTIiwgNzcpLAogICAgICAgICBgRGlzdCBOdW1gPSByZXBsYWNlKGBEaXN0IE51bWAsIGBEaXN0cmljdCBOYW1lYD09Ik1JTk5FQVBPTElTIFBVQkxJQyBTQ0ggRElTVCIsIDk5OTEpLAogICAgICAgICBgRGlzdCBOdW1gPSByZXBsYWNlKGBEaXN0IE51bWAsIGBEaXN0cmljdCBOYW1lYD09Ik1JTk5FT1RBIFBVQkxJQyBTQ0hPT0xTIiwgNDE0KSwKICAgICAgICAgYERpc3QgTnVtYD0gcmVwbGFjZShgRGlzdCBOdW1gLCBgRGlzdHJpY3QgTmFtZWA9PSJQTEFJTlZJRVcgRUxHSU4gTUlMTFZJTExFIFNEIiwgMjg5OSksCiAgICAgICAgIGBEaXN0IE51bWA9IHJlcGxhY2UoYERpc3QgTnVtYCwgYERpc3RyaWN0IE5hbWVgPT0iUk9ZQUxUT04gUFVCTElDIFNDSE9PTFMiLCA0ODUpLAogICAgICAgICBgRGlzdCBOdW1gPSByZXBsYWNlKGBEaXN0IE51bWAsIGBEaXN0cmljdCBOYW1lYD09IldBU0VDQSBQVUJMSUMgU0NIT09MIERJU1QiLCA4MjkpLAogICAgICAgICBgRGlzdCBOdW1gPSByZXBsYWNlKGBEaXN0IE51bWAsIGBEaXN0cmljdCBOYW1lYD09IldBVUJVTi1PR0VNQS1XSElURSBFQVJUSCBQU0QiLCA0MzUpLAogICAgICAgICBgRGlzdCBOdW1gPSByZXBsYWNlKGBEaXN0IE51bWAsIGBEaXN0cmljdCBOYW1lYD09IldJTk9OQSBBUkVBIFBVQkxJQyBTQ0hPT0wgRElTVCIsIDg2MSkpCmBgYAoKCioqQW5udWFsIFN1cnZleSBvZiBTY2hvb2wgU3lzdGVtIEZpbmFuY2VzKioKCkZvcnR1bmF0ZWx5LCB3ZSBkaWQgbm90IGhhdmUgdG8gY2xlYW4gdGhpcyBkYXRhIHNldC4gT3JpZ2luYWxseSwgdGhpcyBkYXRhIHNldCBpbmNsdWRlZCBpbmZvcm1hdGlvbiBhYm91dCBhbGwgb2YgdGhlIHN0YXRlcywgYnV0IHRoZSBwcm9jZXNzIG9mIGRhdGEgam9pbmluZyBuYXJyb3dlZCBkb3duIHRoZSBkYXRhIHRvIGp1c3QgY29udGFpbiBpbmZvcm1hdGlvbiBhYm91dCBNaW5uZXNvdGEuIEJlbG93IGlzIHRoZSBjb2RlIHRoYXQgYWxsb3dlZCB1cyB0byB2aWV3IHRoZSBkYXRhIGVudHJpZXMgcmVsYXRlZCB0byBNaW5uZXNvdGEgYmVmb3JlIGdvaW5nIHRocm91Z2ggdGhlIHByb2Nlc3Mgb2YgZGF0YSBqb2luaW5nLgoKYGBge3J9CnNzICU+JQogICMgZmlsdGVyIGZvciBNTgogIGZpbHRlcihzdHJfZGV0ZWN0KE5DRVNJRCwgIl4yNyIpKSAlPiUKICBhcnJhbmdlKE5DRVNJRCkgJT4lCiAgaGVhZCgxMCkgJT4lCiAgc2VsZWN0KE5DRVNJRCkKYGBgCgoKKipNaW5uZXNvdGEgU2Nob29sIERpc3RyaWN0cyoqCgpUaGlzIGRhdGEgc2V0IG9yaWdpbmFsbHkgaGFkIGZvdXIgY2F0ZWdvcmllczogZGlzdHJpY3QgbmFtZSwgZGlzdHJpY3QgbnVtYmVyLCBzdGFydCBkYXRlLCBhbmQgZW5kIGRhdGUuIFRvIHByZXBhcmUgdGhpcyBkYXRhIHNldCBmb3Igam9pbmluZywgd2UgcmVtb3ZlZCB0aGUgc3RhcnQgYW5kIGVuZCBkYXRlIHZhcmlhYmxlcy4gSW4gb3JkZXIgdG8gam9pbiBpdCB3aXRoIHRoZSBBQ1MgZGF0YSBzZXQsIHdlIGhhZCB0byBjb21wYXJlIHRoZSBkaXN0cmljdCBuYW1lcyBpbiBib3RoIHRvIGVuc3VyZSB0aGF0IHRoZXkgYWxpZ25lZC4gT25seSAxNiBkaXN0cmljdCBuYW1lcyBkaWRuJ3QgbWF0Y2gsIHdoaWNoIHdhcyBhIHNtYWxsIG51bWJlciwgc28gd2UgbWFudWFsbHkgY2hhbmdlZCB0aGUgZGlzdHJpY3QgbmFtZXMgdG8gbWF0Y2ggdGhlIG5hbWVzIGluIHRoZSBBQ1MgZGF0YSBzZXQuCgoKIyBEYXRhIEpvaW5pbmcKClBhcnQgb2YgdGhlIHdvcmsgd2UgZGlkIGFmdGVyIGZpbmRpbmcsIGltcG9ydGluZywgYW5kIGNsZWFuaW5nIG91ciB2YXJpb3VzIGRhdGEgc2V0cyB3YXMgam9pbmluZyB0aGVtIHRvZ2V0aGVyLiBBbiBpbXBvcnRhbnQgcGllY2UgZm9yIHRoaXMgd2FzIGVuc3VyaW5nIHRoYXQgdGhlIGRpc3RyaWN0IG5hbWVzIGFuZCBudW1iZXJzIGFsaWduZWQsIGFuZCBtb3N0IG9mIHRoYXQgd2FzIGNvbXBsZXRlZCBpbiB0aGUgcHJldmlvdXMgc3RlcC4gSGVyZSBpcyBvdXIgY29kZSBmb3Igam9pbmluZyB0aGUgZGF0YSBzZXRzOgoKYGBge3J9CiMgcmVhZCBpbiBkYXRhIHNldHMKIyMgYW5udWFsIHN1cnZleSBvZiBzY2hvb2wgc3lzdGVtIGZpbmFuY2VzICgyMDE4LCBuYXRpb25hbCkKc3MgPC0gcmVhZHhsOjpyZWFkX2V4Y2VsKCJlbHNlYzE4dC54bHMiKQoKIyMgc2VsZWN0ZWQgdmFyaWFibGVzIGZyb20gdGhlIEFDUyAoMjAxOSwgTU4pCm1uX2FjcyA8LSBzdF9yZWFkKCJtbl8yMDE5X2NlbnN1cy9tbl8yMDE5X2NlbnN1cy5zaHAiLCAKICAgICAgICAgICAgICAgICAgZ2VvbWV0cnlfY29sdW1uID0gImdlb21ldHJ5IiwgCiAgICAgICAgICAgICAgICAgIGZpZF9jb2x1bW5fbmFtZSA9ICJnZW9tZXRyeSIpCm1uX2FjcyA8LSBhcy5kYXRhLmZyYW1lKG1uX2FjcykKIyByZW5hbWUgdGhlIGNvbHVtbnMKbmFtZXMobW5fYWNzKSA8LSBjKCJHRU9JRCIsICJtYWxlXzV0bzkiLCAibWFsZV8xMHRvMTQiLCAibWFsZV8xNXRvMTciLAogICAgICAgICAgICAgICAgICAgInRvdGFsX3BvcCIsICJyYWNlX3doaXRlX29ubHkiLCAicmFjZV9CbGFja19vbmx5IiwgInJhY2VfQXNpYW5fb25seSIsCiAgICAgICAgICAgICAgICAgICAicmFjZV9wYWNpZmljX2lzbGFuZGVyX29ubHkiLCAiYXRfbGVhc3RfdHdvX3JhY2VzIiwgIlNTSV9wdWJhc3Npc3RfZm9vZHN0YW1wcyIsCiAgICAgICAgICAgICAgICAgICAibWVkX2hvdXNlaG9sZF9pbmNfMTJtbyIsICJob3VzZV91bml0c193X21vcnRnYWdlIiwgIm1lZF9ncm9zc19yZW50IiwKICAgICAgICAgICAgICAgICAgICJpbnRlcm5ldF9zdWJzY3JpcF9pbl9ob3VzZSIsICJEaXN0cmljdCIsICJwZXJjX21hbGVfNXRvOSIsICJwZXJjX21hbGVfMTB0bzE0IiwKICAgICAgICAgICAgICAgICAgICJwZXJjX21hbGVfMTV0bzE3IiwgInBlcmNfd2hpdGVfb25seSIsICJwZXJjX2JsYWNrX29ubHkiLCAicGVyY19hc2lhbl9vbmx5IiwKICAgICAgICAgICAgICAgICAgICJwZXJjX3BhY2lmaWNfaXNsYW5kZXJfb25seSIsICJwZXJjX2F0X2xlYXN0X3R3b19yYWNlcyIsICJwZXJjX1NTSV9wdWJhc3Npc3RfZm9vZHN0YW1wcyIsCiAgICAgICAgICAgICAgICAgICAicGVyY19ob3VzZV91bml0c193X21vcnRnYWdlIiwgInBlcmNfaW50ZXJuZXRfc3Vic2NyaXB0aW9uIiwKICAgICAgICAgICAgICAgICAgICJEaXN0cmljdC5OYnIiLCAiZ2VvbWV0cnkiKQojIG1vdmUgdGhlIGRpc3RyaWN0IG5hbWUgYW5kIG51bWJlciBjb2xzIHRvIHRoZSBmcm9udAptbl9hY3MgPC0gbW5fYWNzICU+JQogIHJlbG9jYXRlKERpc3RyaWN0LCBEaXN0cmljdC5OYnIsIC5hZnRlciA9IEdFT0lEKQoKIyMgQUNUIHNjb3JlcyAoTU4pCm1uX2FjdCA8LSByZWFkLmNzdigiYWN0X2NsZWFuLmNzdiIpWy0xXSAjIHJlbW92ZSB0aGUgcmVkdW5kYW50IGluZGV4IGNvbHVtbiBjcmVhdGVkIHdoZW4gcmVhZGluZyBieSBjc3YKYGBgCgpKb2luIGJ5IE5DRVMgSUQgZm9yIHNjaG9vbCBkaXN0cmljdHMuCgpgYGB7cn0KIyBsb29rIGF0IHRoaXMgY29sdW1uIGZyb20gdGhlIHNjaG9vbCBzdXJ2ZXkKc3MgJT4lCiAgIyBmaWx0ZXIgZm9yIE1OIAogIGZpbHRlcihzdHJfZGV0ZWN0KE5DRVNJRCwgIl4yNyIpKSAlPiUKICBhcnJhbmdlKE5DRVNJRCkgJT4lCiAgaGVhZCgxMCkgJT4lCiAgc2VsZWN0KE5DRVNJRCkKCiMgYW5kIGZyb20gdGhlIEFDUwptbl9hY3MgJT4lCiAgYXJyYW5nZShHRU9JRCkgJT4lCiAgaGVhZCgxMCkgJT4lCiAgc2VsZWN0KEdFT0lEKQpgYGAKCmBgYHtyfQojIGxlZnQgam9pbiBvbiBBQ1MgZGF0YQptbl9hY3Nfc3MgPC0gbW5fYWNzICU+JQogIGxlZnRfam9pbihzcywgYnkgPSBjKCJHRU9JRCIgPSAiTkNFU0lEIikpCmBgYAoKTGV0J3MgbG9vayBhdCB0aGUgbmFtZSBjb2x1bW5zIGZyb20gdGhlIHR3byBkYXRhIHNldHMgdG8gdmVyaWZ5IHRoYXQgdGhleSBwb2ludCB0byB0aGUgc2FtZSBkaXN0cmljdC4KCmBgYHtyfQptbl9hY3Nfc3MgJT4lCiAgc2VsZWN0KERpc3RyaWN0LCBOQU1FKQpgYGAKCk5vdyBsZXQncyBzZWUgd2hpY2ggcm93cyBmcm9tIHRoZSBBQ1MgYW5kIHNjaG9vbCBzdXJ2ZXkgZGlkbid0IGdldCBhIG1hdGNoLgoKYGBge3J9CiMgQUNTCm1uX2Fjc19zcyAlPiUKICBmaWx0ZXIoaXMubmEoTkFNRSkpCgojIHNjaG9vbCBzdXJ2ZXkKc3MgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QoTkNFU0lELCAiXjI3IikpICU+JSAjIGZpbHRlciBmb3IgTU4KICBhbnRpX2pvaW4obW5fYWNzLCBieSA9IGMoIk5DRVNJRCIgPSAiR0VPSUQiKSkKYGBgCgpTbyBvbmx5IHRoZSByb3cgIlJlbWFpbmRlciBvZiBNaW5uZXNvdGEiIGZyb20gdGhlIEFDUyBkaWRuJ3QgaGF2ZSBhIG1hdGNoLCB3aGljaCBpcyByZWFzb25hYmxlLiBUaGVyZSBhcmUgNjcgc2Nob29sIGRpc3RyaWN0cyBmcm9tIHRoZSBzY2hvb2wgc3VydmV5IHRoYXQgZGlkIG5vdCBqb2luIHRvIHRoZSBBQ1MsIGJ1dCBwZXJoYXBzIHRoZXkgZmFsbCBpbnRvIHRoZSAiUmVtYWluZGVyIG9mIE1pbm5lc290YSIgY2F0ZWdvcnkuIEEgbG90IG9mIHRoZXNlIGRpc3RyaWN0cyBhbHNvIGhhZCAwIGZvciBlbnJvbGxtZW50LgoKVG8gam9pbiB0aGlzIGRhdGEgc2V0IHdpdGggdGhlIEFDVCBkYXRhLCB3ZSdsbCBoYXZlIHRvIHVzZSB0aGUgc2Nob29sIGRpc3RyaWN0IG51bWJlcnMgYXMgdGhlIEFDVCBkYXRhIGRvZXMgbm90IGNvbnRhaW4gdGhlIE5DRVMgSUQuCgpgYGB7cn0KIyBsb29rIGF0IHRoaXMgdmFyaWFibGUgZnJvbSBlYWNoIGRhdGEgc2V0IHRvIHNlZSB0aGVpciBmb3JtYXQKbW5fYWNzX3NzICU+JQogIGFycmFuZ2UoRGlzdHJpY3QuTmJyKSAlPiUKICBzZWxlY3QoRGlzdHJpY3QuTmJyKQoKbW5fYWN0ICU+JQogIGFycmFuZ2UoRGlzdC5OdW0pICU+JQogIHNlbGVjdChEaXN0Lk51bSkKYGBgCgpJdCBhcHBlYXJzIHRoYXQgd2UncmUgbWlzc2luZyBBQ1Qgc2NvcmVzIGZvciAxMSBzY2hvb2wgZGlzdHJpY3RzLiBOZXZlcnRoZWxlc3MsIHRoZSBmb3JtYXRzIGRvIG1hdGNoIHNvIGxldCdzIGpvaW4gdGhlIGRhdGEgc2V0cy4KCmBgYHtyfQptbl9hY3Nfc3NfYWN0IDwtIG1uX2Fjc19zcyAlPiUKICBtdXRhdGUoRGlzdHJpY3QuTmJyID0gYXMuaW50ZWdlcihEaXN0cmljdC5OYnIpKSAlPiUKICBsZWZ0X2pvaW4obW5fYWN0LCBieSA9IGMoIkRpc3RyaWN0Lk5iciIgPSAiRGlzdC5OdW0iKSkKYGBgCgpGaW5hbGx5LCBiZWZvcmUgd2UgY2FuIGV4cG9ydCB0aGUgZGF0YSwgd2Ugd2lsbCByZW1vdmUgdGhlIHJlZHVuZGFudCBuYW1lcyBhbmQgSUQgY29sdW1ucyBmcm9tIHRoZSBzY2hvb2wgc3VydmV5IGFuZCBBQ1QgZGF0YSBzZXRzIGFzIHdlJ2xsIHN0aWNrIHRvIHRoZWlyIGZvcm1hdCBmcm9tIHRoZSBBQ1MuCgpgYGB7cn0KbW5fYWNzX3NzX2FjdCA8LSBtbl9hY3Nfc3NfYWN0ICU+JQogIHNlbGVjdCgtYygiSURDRU5TVVMiLCAiTkFNRSIsICJEaXN0cmljdC5OYW1lIikpCmBgYAoKRXhwb3J0IHRoZSBkYXRhOgoKYGBge3J9CiN3cml0ZS5jc3YobW5fYWNzX3NzX2FjdCwgIm1uX2Fjc19zc19hY3QuY3N2IikKYGBgCgpgYGB7cn0Kc3Rfd3JpdGUobW5fYWNzX3NzX2FjdCwgIm1uX2Fjc19zc19hY3QvbW5fYWNzX3NzX2FjdC5zaHAiLCAKICAgICAgICAgYXBwZW5kPUZBTFNFLCAKICAgICAgICAgZmlkX2NvbHVtbl9uYW1lID0gImdlb21ldHJ5IikKYGBgCgpKb2luIHdpdGggcHJlZGljdG9yIGRhdGEsIHJlcGxhY2UgYWxsIG1pc3NpbmcgZGF0YSBmcm9tIHRoZSBjb3Vyc2UgY2F0YWxvZyB3aXRoIDAsIGFuZCByZW1vdmUgdGhlIHJlZHVuZGFudCB2YXJpYWJsZS4gVGhpcyBpcyB0aGUgZmluYWwgc3RlcCEKCmBgYHtyfQptbl9hY3Nfc3NfYWN0X3ByZWQgPC0gbW5fYWNzX3NzX2FjdCAlPiUKICAjIG11dGF0ZShEaXN0cmljdC5OYnIgPSBhcy5pbnRlZ2VyKERpc3RyaWN0Lk5icikpICU+JSAjIyBubyBsb25nZXIgbmVjZXNzYXJ5IHNpbmNlIHRoZXkncmUgYWxyZWFkeSBpbnRzCiAgIyMgY2hhbmdlZCB0byBsZWZ0IGpvaW4gdG8gaWdub3JlIHJvd3Mgd2l0aG91dCBtYXRjaGVzIGZyb20gdGhlIGNvdXJzZSBjYXRhbG9nCiAgbGVmdF9qb2luKGNvdXJzZV9jYXRhbG9nX3ByZWRpY3RvcnMsIGJ5ID0gYygiRGlzdHJpY3QuTmJyIiA9ICJEaXN0TnVtIikpICU+JSAKICBtdXRhdGUoVG90YWxDbGFzc2VzID0gaWZlbHNlKGlzLm5hKFRvdGFsQ2xhc3NlcyksIDAsIFRvdGFsQ2xhc3NlcyksCiAgICAgICAgIE51bUNhdCA9IGlmZWxzZShpcy5uYShOdW1DYXQpLCAwLCBOdW1DYXQpKSAKYGBgCgoKIyBNYXBwaW5nIGFuZCBTaGFwZSBGaWxlcwoKV2hpbGUgd2UgaGFkIGFsbCB0aGUgZGF0YSBuZWNlc3NhcnkgdG8gY3JlYXRlIHVzZWZ1bCBncmFwaHMgYW5kIHRhYmxlcywgb25lIG9mIG91ciBnb2FscyB3YXMgdG8gY3JlYXRlIHZpc3VhbGl6YXRpb25zIGFuZCBtYXBzLiBPdXIgZGF0YSBpcyBmb2N1c2VkIG9uIHRoZSBzdGF0ZSBvZiBNaW5uZXNvdGEgYW5kIGFsbCBvZiBpdCBpcyBzZWdtZW50ZWQgYnkgc2Nob29sIGRpc3RyaWN0cy4gVGhlcmVmb3JlLCB3ZSBiZWxpZXZlZCB0aGF0IGFuIGludGVyZXN0aW5nIGFuZCBpbmZvcm1hdGl2ZSB3YXkgdG8gcHJlc2VudCB0aGlzIGRhdGEgaXMgdGhyb3VnaCBtYXBzLiBUaGUgcGllY2UgdGhhdCB3YXMgbWlzc2luZyBmb3IgdXMgdG8gYWNjb21wbGlzaCB0aGlzIHdhcyB0aGUgc2hhcGUgZmlsZXMgZm9yIHRoZSBkaXN0cmljdHMuIFNoYXBlIGZpbGVzIGFyZSBub3RvcmlvdXNseSB0cmlja3ksIGFuZCBpbXBsZW1lbnRpbmcgdGhpcyB3YXMgYSBjaGFsbGVuZ2UuCgpPbmNlIHdlIGFkZGVkIGEgY29sdW1uIGZvciB0aGUgZ2VvbWV0cnkgb2YgZWFjaCBkaXN0cmljdCAod2hpY2ggd2FzIG5lY2Vzc2FyeSBmb3IgbWFwcGluZyksIHdlIGNvdWxkIG5vIGxvbmdlciB1c2UgdGhlIHdyaXRlIGFuZCByZWFkX2NzdiBmdW5jdGlvbnMgdGhhdCB3ZSB3ZXJlIHNvIGFjY3VzdG9tZWQgdG8uIFVuZm9ydHVuYXRlbHksIHdlIGZvdW5kIHRoaXMgb3V0IHRoZSBoYXJkIHdheS4gV2hlbiB3ZSBleHBvcnRlZCBvdXIgZmluYWwgZGF0YSBzZXQgdXNpbmcgYHdyaXRlX2NzdigpYCBhbmQgcmVhZCBpdCBpbiBhbm90aGVyIGZpbGUgd2l0aCBgcmVhZF9jc3YoKWAsIG91ciBkYXRhIHZhbHVlcyB3ZXJlIGNvbXBsZXRlbHkgYWx0ZXJlZC4gV2UgaHlwb3RoZXNpemVkIHRoYXQgdGhpcyBjb3VsZCBiZSBkdWUgdG8gaG93IGVhY2ggZ2VvbWV0cnkgZmVhdHVyZSBpcyBzYXZlZCBhcyBhIGxpc3Qgb2YgY29vcmRpbmF0ZXMgc2VwYXJhdGVkIGJ5IGNvbW1hcywgd2hpY2ggd291bGQgaW50ZXJmZXJlIHdpdGggdGhlIGNzdiAoY29tbWEtc2VwYXJhdGVkIHZhbHVlcykgZm9ybWF0LiBGb3J0dW5hdGVseSwgd2UgZGlzY292ZXJlZCB0aGF0IGBzdF93cml0ZSgpYCBhbmQgYHN0X3JlYWQoKWAgZnJvbSB0aGUgYHNmYCBwYWNrYWdlIHdlcmUgdGhlIGZ1bmN0aW9ucyB3ZSBuZWVkZWQgdG8gdXNlIGluc3RlYWQuIFRoZXNlIHdlcmUgc3BlY2lmaWNhbGx5IG1hZGUgZm9yIGV4cG9ydGluZyBhbmQgaW1wb3J0aW5nIGZpbGVzIHdpdGggc2ltcGxlIGdlb21ldHJ5IGZlYXR1cmVzLiBPbmUgaXNzdWUgd2UgZW5jb3VudGVyZWQgd2l0aCBleHBvcnRpbmcgc2hhcGUgZmlsZXMgaXMgdGhhdCBjZXJ0YWluIGNvbHVtbiBuYW1lcyB3ZXJlIGFiYnJldmlhdGVkIGluIHRoZSBwcm9jZXNzLiBGb3IgZXhhbXBsZSwgYG1hbGVfNXRvOWAgYmVjYW1lIGBtYWxfNXQ5YCBhbmQgYHRvdGFsX3BvcGAgYmVjYW1lIGB0b3RsX3BwYC4gSW4gdGhlIGVuZCwgd2UgZGVjaWRlZCB0aGF0IHRoZSBzaW1wbGVzdCBhbmQgZmFzdGVzdCBzb2x1dGlvbiB3YXMgdG8gcmVuYW1lIG91ciB2YXJpYWJsZXMgYmFjayB0byB0aGVpciBvcmlnaW5hbCBuYW1lcyBhZnRlciB3ZSByZWFkIGluIHRoZSBkYXRhLgoKYGBge3J9CiMjIyMjIFJVTiBUSElTIENIVU5LIFdIRU5FVkVSIFlPVSBSRUFEIElOIFRIRSBEQVRBCm1uX2Fjc19zc19hY3RfcHJlZCA8LSBzdF9yZWFkKCJtbl9hY3Nfc3NfYWN0X3ByZWQvbW5fYWNzX3NzX2FjdF9wcmVkLnNocCIsCiAgICAgICAgICAgICAgICBnZW9tZXRyeV9jb2x1bW4gPSAiZ2VvbWV0cnkiLAogICAgICAgICAgICAgICAgZmlkX2NvbHVtbl9uYW1lID0gImdlb21ldHJ5IiwKICAgICAgICAgICAgICAgIHF1aWV0ID0gVFJVRSkKIyByZW5hbWUgdGhlIGNlbnN1cyB2YXJpYWJsZXMgYmVjYXVzZSB0aGVpciBuYW1lcyB3ZXJlIHJlZm9ybWF0dGVkLi4uCm5hbWVzKG1uX2Fjc19zc19hY3RfcHJlZClbMToyOF0gPC0gCiAgYygiR0VPSUQiLCAiRGlzdHJpY3QiLCAiRGlzdHJpY3QuTmJyIiwgIm1hbGVfNXRvOSIsICJtYWxlXzEwdG8xNCIsICJtYWxlXzE1dG8xNyIsCiAgICAidG90YWxfcG9wIiwgInJhY2Vfd2hpdGVfb25seSIsICJyYWNlX0JsYWNrX29ubHkiLCAicmFjZV9Bc2lhbl9vbmx5IiwKICAgICJyYWNlX3BhY2lmaWNfaXNsYW5kZXJfb25seSIsICJhdF9sZWFzdF90d29fcmFjZXMiLCAiU1NJX3B1YmFzc2lzdF9mb29kc3RhbXBzIiwKICAgICJtZWRfaG91c2Vob2xkX2luY18xMm1vIiwgImhvdXNlX3VuaXRzX3dfbW9ydGdhZ2UiLCAibWVkX2dyb3NzX3JlbnQiLAogICAgImludGVybmV0X3N1YnNjcmlwX2luX2hvdXNlIiwgInBlcmNfbWFsZV81dG85IiwgInBlcmNfbWFsZV8xMHRvMTQiLAogICAgInBlcmNfbWFsZV8xNXRvMTciLCAicGVyY193aGl0ZV9vbmx5IiwgInBlcmNfYmxhY2tfb25seSIsICJwZXJjX2FzaWFuX29ubHkiLAogICAgInBlcmNfcGFjaWZpY19pc2xhbmRlcl9vbmx5IiwgInBlcmNfYXRfbGVhc3RfdHdvX3JhY2VzIiwgInBlcmNfU1NJX3B1YmFzc2lzdF9mb29kc3RhbXBzIiwKICAgICJwZXJjX2hvdXNlX3VuaXRzX3dfbW9ydGdhZ2UiLCAicGVyY19pbnRlcm5ldF9zdWJzY3JpcHRpb24iKQpuYW1lcyhtbl9hY3Nfc3NfYWN0X3ByZWQpWzkzXSA8LSBjKCJUb3RhbENsYXNzZXMiKQpgYGAKCgojIFZpc3VhbGl6YXRpb25zIGFuZCBQbG90bHkKCkFsdGhvdWdoIHdlIGtuZXcgb3VyIG1hcHMgdXAgdG8gdGhpcyBwb2ludCB3ZXJlIHRlbGxpbmcsIHdlIHdhbnRlZCB0byBhZGQgYW5vdGhlciBlbGVtZW50IHRvIG91ciBwcm9qZWN0LiBXZSBkZWNpZGVkIHRvIG1ha2UgdGhlIG1hcHMgaW50ZXJhY3RpdmUgYW5kIGNob3NlIHRvIHVzZSBwbG90bHkgdG8gYWNoaWV2ZSB0aGlzLiBUaGUgcGxvdGx5IGZ1bmN0aW9ucyBoYWQgYW4gaXNzdWUgd2l0aCBvdXIgZmluYWwgZGF0YSBzZXQgYW5kIHdvdWxkIG5vdCBtYXAgdW5sZXNzIHdlIHJlbW92ZWQgYWxsIHJvd3Mgd2l0aCBOQTsgY29udmVydGluZyB0aGUgTkEgdmFsdWVzIHRvIDAgZGlkIG5vdCBmaXggdGhlIGlzc3VlLCBhbmQgbmVpdGhlciBkaWQgY29udmVydGluZyB0aGUg4oCYTuKAmSB2YWx1ZXMgd2UgZm91bmQgaW4gdHdvIHZhcmlhYmxlIGNvbHVtbnMgdG8gMC4gSW4gdG90YWwsIGRyb3BwaW5nIHRoZSBOQSByb3dzIHJlbW92ZWQgMTEgZGlzdHJpY3RzLiBXaXRoIG1vcmUgdGltZSwgdGhpcyBpc3N1ZSBzaG91bGQgYmUgZXhwbG9yZWQgZnVydGhlci4gU29tZXRoaW5nIHRoYXQgaXMgaW1wb3J0YW50IHRvIG5vdGUgaXMgdGhhdCB0aGUgZGlzdHJpY3RzIHRoYXQgd2VyZSBub3QgbWFwcGVkIHN0aWxsIHNob3cgaW50ZXJhY3Rpdml0eSBiYXNlZCBvbiB3aGljaGV2ZXIgZGlzdHJpY3QgaXMgY2xvc2VzdCB0byB0aGUgcG9pbnRlci4KCkJlbG93IHdlIGhhdmUgdHdvIGV4YW1wbGVzIGZyb20gb3VyIGZpbmFsIHByb2R1Y3Q6IG9uZSBvZiBhIHN0YXRpYyBncmFwaCBhbmQgb25lIG9mIGEgcGxvdGx5IGdyYXBoLiBNb3N0IG9mIG91ciB2aXN1YWxpemF0aW9ucyBhbmQgbWFwcyB3ZXJlIGJ1aWx0IGluIHRoZSBzYW1lIHdheSwgc28gd2UgZGlkIG5vdCBpbmNsdWRlIGFsbCBvZiB0aGUgdmlzdWFsaXphdGlvbiBjb2RlIGluIHRoaXMgZG9jdW1lbnQuCgoqKlN0YXRpYyBHcmFwaCoqCmBgYHtyfQojcG9wdWxhdGlvbgpnZ3Bsb3QobW5fYWNzX3NzX2FjdF9wcmVkLCBhZXMoZmlsbCA9IHRvdGFsX3BvcCkpICsgCiAgZ2VvbV9zZihjb2xvciA9IE5BKSArIAogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJtYWdtYSIsIGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsKICBsYWJzKHRpdGxlID0gIlRvdGFsIFBvcHVsYXRpb24iLCBmaWxsID0gIiIpICsKICB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKYGBgCgoKKipQbG90bHkqKgpgYGB7cn0KbW5fYWNzX3NzX2FjdF9wcmVkX3ZpeiA8LSAKICBtbl9hY3Nfc3NfYWN0X3ByZWQgJT4lICBkcm9wX25hKCkKCmdncGxvdGx5KAogIGdncGxvdChtbl9hY3Nfc3NfYWN0X3ByZWRfdml6KSArCiAgZ2VvbV9zZihhZXMoZmlsbCA9IFRvdGFsQ2xhc3NlcywgdGV4dCA9IHBhc3RlKERpc3RyaWN0KSksICBzaXplID0gLjEsIGNvbG9yID0gIndoaXRlIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJCIikgKwogIGxhYnModGl0bGUgPSAiVG90YWwgQ1MgQ291cnNlcyBPZmZlcmVkIiwgZmlsbCA9ICIiKSArCiAgdGhlbWUoYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgdGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTEyLCBmYWNlPSdib2xkJykpCikgCmBgYAoKCiMgTW9kZWxpbmcKClRoZSBnb2FsIG9mIG91ciBtb2RlbCB3YXMgdG8gcHJlZGljdCB0aGUgbnVtYmVyIG9mIGNvbXB1dGVyIHNjaWVuY2UgY291cnNlcyBvZmZlcmVkIHBlciBkaXN0cmljdCBpbiBNaW5uZXNvdGEgYmFzZWQgb24gdGhlIHZhcmlhYmxlcyBmcm9tIG91ciBBQ1MsIEFTU1NGLCBhbmQgQUNUIGRhdGEuCgpgYGB7ciBwYWdlZC10YWJsZSwgZWNobz1GQUxTRX0KbGlicmFyeShrbml0cikKa25pdF9wcmludC5kYXRhLmZyYW1lIDwtIGZ1bmN0aW9uKHgsIC4uLikgewogIGFzaXNfb3V0cHV0KAogICAgcm1hcmtkb3duOjo6cGFnZWRfdGFibGVfaHRtbCh4LCBvcHRpb25zID0gYXR0cih4LCAib3B0aW9ucyIpKSwKICAgIG1ldGEgPSBsaXN0KGRlcGVuZGVuY2llcyA9IHJtYXJrZG93bjo6Omh0bWxfZGVwZW5kZW5jeV9wYWdlZHRhYmxlKCkpCiAgKQp9CnJlZ2lzdGVyUzNtZXRob2QoImtuaXRfcHJpbnQiLCAiZGF0YS5mcmFtZSIsIGtuaXRfcHJpbnQuZGF0YS5mcmFtZSkKYGBgCgpXZSBmaXJzdCByZWFkIGluIHRoZSBkYXRhIGFuZCBjbGVhbmVkIHVwIHZhcmlhYmxlIG5hbWVzLCBhcyB0aGV5IHdlcmUgYWJicmV2aWF0ZWQgYWZ0ZXIgYmVpbmcgZXhwb3J0ZWQgdG8gc2hhcGUgZmlsZSBmb3JtYXQuCgpgYGB7cn0KIyByZWFkIGluIHRoZSBkYXRhCm1uIDwtIHN0X3JlYWQoIm1uX2Fjc19zc19hY3RfcHJlZC9tbl9hY3Nfc3NfYWN0X3ByZWQuc2hwIiwKICAgICAgICAgICAgICBnZW9tZXRyeV9jb2x1bW4gPSAiZ2VvbWV0cnkiLAogICAgICAgICAgICAgIGZpZF9jb2x1bW5fbmFtZSA9ICJnZW9tZXRyeSIpCiMgcmVuYW1lIHRoZSBjZW5zdXMgdmFyaWFibGVzIGJlY2F1c2UgdGhlaXIgbmFtZXMgd2VyZSByZWZvcm1hdHRlZC4uLgpuYW1lcyhtbilbMToyOF0gPC0gCiAgYygiR0VPSUQiLCAiRGlzdHJpY3QiLCAiRGlzdHJpY3QuTmJyIiwgIm1hbGVfNXRvOSIsICJtYWxlXzEwdG8xNCIsICJtYWxlXzE1dG8xNyIsCiAgICAidG90YWxfcG9wIiwgInJhY2Vfd2hpdGVfb25seSIsICJyYWNlX0JsYWNrX29ubHkiLCAicmFjZV9Bc2lhbl9vbmx5IiwKICAgICJyYWNlX3BhY2lmaWNfaXNsYW5kZXJfb25seSIsICJhdF9sZWFzdF90d29fcmFjZXMiLCAiU1NJX3B1YmFzc2lzdF9mb29kc3RhbXBzIiwKICAgICJtZWRfaG91c2Vob2xkX2luY18xMm1vIiwgImhvdXNlX3VuaXRzX3dfbW9ydGdhZ2UiLCAibWVkX2dyb3NzX3JlbnQiLAogICAgImludGVybmV0X3N1YnNjcmlwX2luX2hvdXNlIiwgInBlcmNfbWFsZV81dG85IiwgInBlcmNfbWFsZV8xMHRvMTQiLAogICAgInBlcmNfbWFsZV8xNXRvMTciLCAicGVyY193aGl0ZV9vbmx5IiwgInBlcmNfYmxhY2tfb25seSIsICJwZXJjX2FzaWFuX29ubHkiLAogICAgInBlcmNfcGFjaWZpY19pc2xhbmRlcl9vbmx5IiwgInBlcmNfYXRfbGVhc3RfdHdvX3JhY2VzIiwgInBlcmNfU1NJX3B1YmFzc2lzdF9mb29kc3RhbXBzIiwKICAgICJwZXJjX2hvdXNlX3VuaXRzX3dfbW9ydGdhZ2UiLCAicGVyY19pbnRlcm5ldF9zdWJzY3JpcHRpb24iKQpuYW1lcyhtbilbOTNdIDwtIGMoIlRvdGFsQ2xhc3NlcyIpCmBgYAoKCiMjIyBEYXRhIEV4cGxvcmF0aW9uCgpgYGB7cn0KIyBsb29rIGF0IHRoZSB2YXJpYWJsZXMgYW5kIHRoZWlyIHR5cGVzCnN0cihtbikKYGBgCgpBZnRlciBsb29raW5nIGF0IHRoZSB2YXJpYWJsZXMgYW5kIHRoZWlyIHR5cGVzLCB3ZSBub3RpY2VkIHRoYXQgd2Ugd2VyZSBkZWFsaW5nIHdpdGggcHJlZG9taW5hbnRseSBudW1lcmljYWwgdmFyaWFibGVzLiBgR0VPSURgLCBgRGlzdHJpY3RgLCBgRGlzdHJpY3QuTmJyYCwgYENPTlVNYCwgYENTQWAsIGFuZCBgQ0JTQWAgYXJlIElEIHZhcmlhYmxlcywgc28gd2Ugd2lsbCBoYXZlIHRvIHNwZWNpZnkgdGhhdCBpbiB0aGUgcmVjaXBlIHNvIHRoYXQgdGhleSB3b24ndCBiZSBpbmNsdWRlZCBpbiB0aGUgbW9kZWwuCgpXZSBkZWNpZGVkIHRvIGRyb3AgdGhlIGdlb21ldHJ5IGNvbHVtbiBhbmQgY29udmVydCB0aGUgZGF0YSB0byBhIHJlZ3VsYXIgZGF0YSBmcmFtZSBmb3JtYXQgZm9yIHRoaXMgcGFydCBiZWNhdXNlIGl0IHdhcyBub3QgbmVjZXNzYXJ5IGZvciB0aGUgbW9kZWxzIGFuZCBpbmNsdWRpbmcgaXQgbGVkIHRvIGlzc3VlcyBydW5uaW5nIHNvbWUgZnVuY3Rpb25zLgoKYGBge3J9Cm1uIDwtIG1uICU+JQogIHN0X2Ryb3BfZ2VvbWV0cnkoKQpgYGAKCldlIHBsb3R0ZWQgdGhlIGRpc3RyaWJ1dGlvbnMgb2Ygb3VyIHZhcmlhYmxlcyB0byBzZWUgd2hhdCBraW5kIG9mIGRhdGEgdHJhbnNmb3JtYXRpb24gd2Ugd291bGQgbmVlZCB0byBwZXJmb3JtLgoKQmVsb3cgYXJlIHNldHMgb2Ygb3VyIHByZWRpY3RvcnMsIGZpcnN0IGZyb20gdGhlIEFubnVhbCBTdXJ2ZXkgb2YgU2Nob29sIFN5c3RlbSBGaW5hbmNlczoKCmBgYHtyIGV4cGwtY29udC1zcywgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTh9Cm1uICU+JQogIHNlbGVjdCgtR0VPSUQpICU+JQogICMgdmFyaWFibGVzIGZyb20gdGhlIEFubnVhbCBTdXJ2ZXkgb2YgU2Nob29sIFN5c3RlbSBGaW5hbmNlcyBhcmUgaW4gYWxsIGNhcHMKICBzZWxlY3QobWF0Y2hlcygiXltBLVpdezQsfSIsIGlnbm9yZS5jYXNlID0gRkFMU0UpKSAlPiUKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJ2YXJpYWJsZSIsCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZXMiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZXMpKSArCiAgZ2VvbV9oaXN0b2dyYW0oKSArCiAgZmFjZXRfd3JhcCh2YXJzKHZhcmlhYmxlKSwKICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlIikKYGBgCgpOb3cgZnJvbSB0aGUgQUNTIGFuZCBBQ1QgZGF0YToKCmBgYHtyIGV4cGwtY29udC1hY3MtYWN0LCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OH0KbW4gJT4lCiAgIyByZW1vdmUgdGhlIElEIHZhcmlhYmxlcwogIHNlbGVjdCgtYygiR0VPSUQiLCAiRGlzdHJpY3QiLCAiRGlzdHJpY3QuTmJyIikpICU+JQogICMgdGhlc2UgdmFyaWFibGVzIGhhdmUgYXQgbGVhc3Qgb25lIGxvd2VyIGNhc2UgY2hhcmFjdGVyCiAgc2VsZWN0KG1hdGNoZXMoIlthLXpdIiwgaWdub3JlLmNhc2UgPSBGQUxTRSkpICU+JQogICMgZmlsdGVyIGZvciBudW1lcmljYWwgdmFyaWFibGVzCiAgc2VsZWN0KHdoZXJlKGlzLm51bWVyaWMpKSAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWVzIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdmFsdWVzKSkgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksCiAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKQSBsb3Qgb2YgdGhlIGRhdGEgYXJlIHJpZ2h0LXNrZXdlZCwgd2hpY2ggbWFrZXMgc2Vuc2UgYXMgbWFueSB2YXJpYWJsZXMgYXJlIHJhdyBjb3VudHMuCgpXZSBhbHNvIGNoZWNrZWQgZm9yIG1pc3NpbmcgZGF0YToKCmBgYHtyIG1pc3NpbmctZGF0YX0KbW4gJT4lIAogIGFkZF9uX21pc3MoKSAlPiUgCiAgY291bnQobl9taXNzX2FsbCkKYGBgCgpXZSBoYWQgb25lIG9ic2VydmF0aW9uIG1pc3NpbmcgNjUgdmFyaWFibGVzLCBhbmQgdGhpcyB3YXMgdGhlICJSZW1haW5kZXIgb2YgTWlubmVzb3RhIiBvYnNlcnZhdGlvbiBmcm9tIHRoZSBBQ1MuIFRoZSBOQXMgY2F1c2VkIGlzc3VlcyB3aGVuIHdlIHRyaWVkIHRvIGZpdCBhIExBU1NPIGFuZCByYW5kb20gZm9yZXN0IG1vZGVsLCBzbyB3ZSByZW1vdmVkIHRoZW0gYmVmb3JlaGFuZC4KCmBgYHtyfQptbiA8LSBtbiAlPiUKICBkcm9wX25hKCkKYGBgCgoKIyMjIE1vZGVsIFJlY2lwZQoKYGBge3IgaW5jbHVkZT1GQUxTRX0KIyBzZXQgdGhlIHNlZWQKc2V0LnNlZWQoMjEpCmBgYAoKRnJvbSBvdXIgZGF0YSBleHBsb3JhdGlvbiwgdGhlc2Ugd2VyZSB0aGUgdGhpbmdzIHdlIGhhZCB0byBkbyBpbiB0aGUgcmVjaXBlOgoKKiBMb2ctdHJhbnNmb3JtIG1vc3Qgb2YgdGhlIHZhcmlhYmxlcyAoZXhjZXB0IGZvciB0aGUgb25lcyB0aGF0IHN0YXJ0IHdpdGggUENUIGZyb20gdGhlIFNjaG9vbCBTdXJ2ZXkgYXMgdGhleSdyZSBwZXJjZW50YWdlcyBhbmQgUFAgYXMgdGhleSdyZSBzcGVuZGluZyBwZXIgcHVwaWwgYW5kIG5vdCByaWdodC1za2V3ZWQpLgoqIFVzZSBwZXJjZW50YWdlcyBmb3IgQUNTIHZhcmlhYmxlcy4KKiBOb3JtYWxpemUgYWxsIG51bWVyaWNhbCB2YXJpYWJsZXMuCiogSWdub3JlIGBQQ1RUT1RBYCBhbmQgYExPQ1JQQVJgIGFzIGFsbCB2YXJpYWJsZXMgaGF2ZSB0aGUgc2FtZSB2YWx1ZS4KKiBNYWtlIHRoZSBJRCB2YXJpYWJsZXMgZXZhbHVhdGl2ZSAoaS5lLiBub3QgaW5jbHVkZWQgaW4gbW9kZWxpbmcpLgoKCioqU3BsaXQgZGF0YSoqCgpXZSBzcGxpdCB0aGUgZGF0YSBpbnRvIGEgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0IGFuZCBhbHNvIGNyZWF0ZWQgY3Jvc3MtdmFsaWRhdGlvbiBmb2xkcyBmb3IgZXZhbHVhdGlvbi4KCmBgYHtyIHNwbGl0fQojIHNwbGl0IHRoZSBkYXRhIGludG8gYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQKbW5fc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChtbiwgcHJvcCA9IC43NSkKbW5fdHJhaW5pbmcgPC0gdHJhaW5pbmcobW5fc3BsaXQpCm1uX3Rlc3RpbmcgPC0gdGVzdGluZyhtbl9zcGxpdCkKYGBgCgpgYGB7ciBjdn0KIyBjcmVhdGUgY3Jvc3MtdmFsaWRhdGlvbiBmb2xkcwptbl9jdiA8LSB2Zm9sZF9jdihtbl90cmFpbmluZywgdiA9IDUpCmBgYAoKCioqUHJlcHJvY2VzcyBkYXRhKioKCkhlcmUgaXMgdGhlIHJlY2lwZSBmb3Igb3VyIG1vZGVsczoKCmBgYHtyIHJlY2lwZX0KbW5fcmVjaXBlIDwtIAogIHJlY2lwZShUb3RhbENsYXNzZXMgfiAuLCBkYXRhID0gbW5fdHJhaW5pbmcpICU+JQogICMgaWdub3JlIG9ic2VydmF0aW9ucyB3aXRoIG1pc3NpbmcgZGF0YSAobmVjZXNzYXJ5IGZvciBMQVNTTyBtb2QpCiAgc3RlcF9uYW9taXQoZXZlcnl0aGluZygpLCBza2lwID0gVFJVRSkgJT4lCiAgIyByZW1vdmUgdmFyaWFibGVzCiAgc3RlcF9ybSgKICAgICMgdGhpcyBvbmUgY2FuIGJlIGNvbnNpZGVyZWQgYXMgcmVzcG9uc2UgdmFyaWFibGUgaXRzZWxmCiAgICBOdW1DYXQsIAogICAgIyBhbGwgdmFyaWFibGVzIGhhdmUgdGhlIHNhbWUgdmFsdWUgZm9yIHRoZXNlIHR3bwogICAgUENUVE9UQSwgTE9DUlBBUiwgCiAgICAjIHJhdyBjb3VudHMgZnJvbSBBQ1MKICAgIG1hdGNoZXMoIlthLXpdIiwgaWdub3JlLmNhc2UgPSBGQUxTRSksCiAgICAtc3RhcnRzX3dpdGgoInBlcmMiKSwKICAgIC10b3RhbF9wb3AsCiAgICAtQXZnX0NtcCwKICAgIC1Ub3RhbENsYXNzZXMKICAgICkgJT4lCiAgIyBsb2ctdHJhbnNmb3JtIAogIHN0ZXBfbG9nKAogICAgIyB0b3RhbCBwb3B1bGF0aW9uCiAgICB0b3RhbF9wb3AsCiAgICAjIHNwZW5kaW5nIC8gcmV2ZW51ZSB2YXJpYWJsZXMgZnJvbSB0aGUgc2Nob29sIHN1cnZleQogICAgIyMgaWdub3JlIHRob3NlIHRoYXQgc3RhcnQgd2l0aCBQIHNpbmNlIHRoZXkncmUgcGVyY2VudGFnZXMgLyBzcGVuZGluZyBwZXIgc3R1ZGVudAogICAgbWF0Y2hlcygiXltBLU9RLVpdezQsfSIsIGlnbm9yZS5jYXNlID0gRkFMU0UpLCAKICAgICMjIGlnbm9yZSBJRCB2YXJpYWJsZXMgYXMgd2VsbAogICAgLUdFT0lELCAtQ09OVU0sIC1DQlNBLAogICAgIyBzb21lIHZhcmlhYmxlcyBoYXZlIDBzIHdoaWNoIHdpbGwgcHJvZHVjZSBOYU5zIHdoZW4gbG9nLXRyYW5zZm9ybWVkCiAgICBvZmZzZXQgPSAxKSAlPiUgCiAgIyBtYWtlIElEIHZhcmlhYmxlcyBldmFsdWF0aXZlIChub3QgaW5jbHVkZWQgaW4gbW9kZWxpbmcpCiAgdXBkYXRlX3JvbGUoCiAgICBhbGxfb2YoYygiR0VPSUQiLAogICAgICAgICAgICAgIkRpc3RyaWN0IiwKICAgICAgICAgICAgICJEaXN0cmljdC5OYnIiLAogICAgICAgICAgICAgIkNPTlVNIiwKICAgICAgICAgICAgICJDU0EiLAogICAgICAgICAgICAgIkNCU0EiKSksCiAgICBuZXdfcm9sZSA9ICJldmFsdWF0aXZlIikgJT4lCiAgIyBtYWtlIGludGVnZXJzIG51bWVyaWMKICBzdGVwX211dGF0ZV9hdChpcy5pbnRlZ2VyLCBmbiA9IGFzLm51bWVyaWMpICU+JQogICMgbm9ybWFsaXplIG51bWVyaWNhbCB2YXJpYWJsZXMKICBzdGVwX25vcm1hbGl6ZShhbGxfcHJlZGljdG9ycygpKQpgYGAKCkhlcmUgaXMgd2hhdCB0aGUgZGF0YSBsb29rZWQgbGlrZSBwb3N0LXRyYW5zZm9ybWF0aW9uOgoKYGBge3J9Cm1uX3JlY2lwZSAlPiUKICBwcmVwKG1uX3RyYWluaW5nKSAlPiUKICBqdWljZSgpCmBgYAoKCiMjIyBNb2RlbCBGaXR0aW5nCgoqKlJlZ3VsYXIgbGluZWFyIHJlZ3Jlc3Npb24qKgoKV2UgZmlyc3QgdGVzdGVkIGEgcmVndWxhciBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBhbmQgbG9va2VkIGF0IHRoZSB0YWJsZSBvZiBjb2VmZmljaWVudHMuIFNpbmNlIHdlIGhhZCBhbiBvdmVyd2hlbG1pbmcgbnVtYmVyIG9mIHByZWRpY3RvcnMsIHdlIGFzc3VtZWQgYmVmb3JlaGFuZCB0aGF0IHRoaXMgbW9kZWwgd291bGQgbm90IHBlcmZvcm0gd2VsbCBkdWUgdG8gb3ZlcmZpdHRpbmcuCgpgYGB7ciBsbX0KIyBkZWZpbmUgdGhlIG1vZGVsIHR5cGUKbW5fbGluZWFyX21vZCA8LQogIGxpbmVhcl9yZWcoKSAlPiUKICBzZXRfZW5naW5lKCJsbSIpICU+JQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikKCiMgc2V0IHVwIHRoZSB3b3JrZmxvdwptbl9sbV93ZiA8LQogIHdvcmtmbG93KCkgJT4lCiAgYWRkX3JlY2lwZShtbl9yZWNpcGUpICU+JQogIGFkZF9tb2RlbChtbl9saW5lYXJfbW9kKQoKIyBmaXQgdGhlIG1vZGVsCm1uX2xtX2ZpdCA8LQogIG1uX2xtX3dmICU+JQogIGZpdChtbl90cmFpbmluZykKCiMgZGlzcGxheSB0aGUgcmVzdWx0cwptbl9sbV9maXQgJT4lIAogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIAogIHRpZHkoKSAlPiUgCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfnJvdW5kKC54LDMpKSkgCmBgYAoKCioqTEFTU08qKgoKVG8gZGVhbCB3aXRoIHRoZSBtYXNzaXZlIG51bWJlciBvZiBwcmVkaWN0b3JzLCB3ZSBzd2l0Y2hlZCB0byBMQVNTTyB0byBzaHJpbmsgY29lZmZpY2llbnRzIHRvIHplcm8gYW5kIHRoZXJlYnkgZWxpbWluYXRlIGluc2lnbmlmaWNhbnQgdmFyaWFibGVzIGZyb20gdGhlIG1vZGVsLgoKYGBge3IgbGFzc299CiMgZGVmaW5lIHRoZSBtb2RlbCB0eXBlCm1uX2xhc3NvX21vZCA8LQogIGxpbmVhcl9yZWcobWl4dHVyZSA9IDEpICU+JQogIHNldF9lbmdpbmUoImdsbW5ldCIpICU+JQogIHNldF9hcmdzKHBlbmFsdHkgPSB0dW5lKCkpICU+JQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikKCiMgc2V0IHVwIHRoZSB3b3JrZmxvdwptbl9sYXNzb193ZiA8LQogIHdvcmtmbG93KCkgJT4lCiAgYWRkX3JlY2lwZShtbl9yZWNpcGUpICU+JQogIGFkZF9tb2RlbChtbl9sYXNzb19tb2QpCgojIHNldCB1cCBwZW5hbHR5IGdyaWQgZm9yIHR1bmluZwpwZW5hbHR5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKHBlbmFsdHkoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSAyMCkKCiMgdHVuZSB0aGUgcGFyYW1ldGVyCm1uX2xhc3NvX3R1bmUgPC0KICBtbl9sYXNzb193ZiAlPiUKICB0dW5lX2dyaWQoCiAgICByZXNhbXBsZXMgPSBtbl9jdiwKICAgIGdyaWQgPSBwZW5hbHR5X2dyaWQKICApCmBgYAoKV2UgY2hvc2UgdGhlIGJlc3QgcGFyYW1ldGVyIGJhc2VkIG9uIFJNU0UgYW5kIGZpbmFsaXplZCB0aGUgd29ya2Zsb3cgLyBtb2RlbC4gV2UgdGhlbiBsb29rZWQgYXQgdGhlIHZhcmlhYmxlcyB0aGF0IHdlcmUgcmV0YWluZWQgYnkgTEFTU08uCgpgYGB7cn0KIyBzaG93IHRoZSBiZXN0IHBlbmFsdHkgcGFyYW1ldGVyCm1uX2xhc3NvX3R1bmUgJT4lIAogIHNob3dfYmVzdChtZXRyaWMgPSAicm1zZSIpCgojIHNlbGVjdCBiZXN0IHBhcmFtZXRlciBieSBzbWFsbGVzdCBybXNlCihiZXN0X3BhcmFtIDwtIG1uX2xhc3NvX3R1bmUgJT4lIAogICAgc2VsZWN0X2Jlc3QobWV0cmljID0gInJtc2UiKSkKYGBgCgpgYGB7cn0KIyBmaW5hbGl6ZSB3b3JrZmxvdwptbl9sYXNzb19maW5hbF93ZiA8LSBtbl9sYXNzb193ZiAlPiUgCiAgZmluYWxpemVfd29ya2Zsb3coYmVzdF9wYXJhbSkKCiMgZml0IGZpbmFsIG1vZGVsCm1uX2xhc3NvX2ZpbmFsX21vZCA8LQogIG1uX2xhc3NvX2ZpbmFsX3dmICU+JQogIGZpdChkYXRhID0gbW5fdHJhaW5pbmcpCgojIGxvb2sgYXQgdGhlIHRhYmxlIG9mIGNvZWZmaWNpZW50cwptbl9sYXNzb19maW5hbF9tb2QgJT4lIAogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIAogIHRpZHkoKSAgJT4lCiAgIyBmaWx0ZXIgZm9yIHByZWRpY3RvcnMgd2l0aCBub24temVybyBjb2VmZmljaWVudHMgCiAgZmlsdGVyKGVzdGltYXRlICE9IDApCmBgYAoKCioqUmFuZG9tIGZvcmVzdCoqCgpPdXIgbGFzdCBtb2RlbCBjYW5kaWRhdGUgd2FzIGEgcmFuZG9tIGZvcmVzdCBtb2RlbC4KCmBgYHtyfQojIGRlZmluZSB0aGUgbW9kZWwgdHlwZQptbl9yZl9tb2QgPC0gCiAgcmFuZF9mb3Jlc3QobXRyeSA9IDIzLCAjIH4xLzMgb2YgcHJlZGljdG9ycyAKICAgICAgICAgICAgICBtaW5fbiA9IDUsIAogICAgICAgICAgICAgIHRyZWVzID0gMjAwKSAlPiUgCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikKCiMgc2V0IHVwIHRoZSB3b3JrZmxvdwptbl9yZl93ZiA8LQogIHdvcmtmbG93KCkgJT4lCiAgYWRkX3JlY2lwZShtbl9yZWNpcGUpICU+JQogIGFkZF9tb2RlbChtbl9yZl9tb2QpCgojIGZpdCB0aGUgbW9kZWwKbW5fcmZfZml0IDwtIAogIG1uX3JmX3dmICU+JSAKICBmaXQobW5fdHJhaW5pbmcpCmBgYAoKCiMjIyBNb2RlbCBFdmFsdWF0aW9uIGFuZCBDb21wYXJpc29uCiAKYGBge3J9CiMgZml0IG1vZGVsIHdpdGggYmVzdCB0dW5pbmcgcGFyYW1ldGVyKHMpIHRvIHRyYWluaW5nIGRhdGEgYW5kIGFwcGx5IHRvIHRlc3QgZGF0YQptbl9sbV90ZXN0IDwtIAogIG1uX2xtX3dmICU+JSAKICBsYXN0X2ZpdChtbl9zcGxpdCkKbW5fbGFzc29fdGVzdCA8LSAKICBtbl9sYXNzb19maW5hbF93ZiAlPiUgCiAgbGFzdF9maXQobW5fc3BsaXQpCm1uX3JmX3Rlc3QgPC0KICBtbl9yZl93ZiAlPiUKICBsYXN0X2ZpdChtbl9zcGxpdCkKYGBgCgpPZiBvdXIgdGhyZWUgbW9kZWxzLCB0aGUgcmFuZG9tIGZvcmVzdCBwZXJmb3JtZWQgYmVzdCwgZm9sbG93ZWQgYnkgTEFTU08sIGFuZCB0aGVuIHJlZ3VsYXIgbGluZWFyIHJlZ3Jlc3Npb24uCgpgYGB7cn0KIyBjb2xsZWN0IG1ldHJpY3MgZm9yIG1vZGVsIGFwcGxpZWQgdG8gdGVzdCBkYXRhCm1uX2xtX3Rlc3QgJT4lCiAgY29sbGVjdF9tZXRyaWNzCm1uX2xhc3NvX3Rlc3QgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpCm1uX3JmX3Rlc3QgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpCmBgYAoKCioqUmVzaWR1YWxzKioKClNpbmNlIHRoZSBSTVNFIGZyb20gdGhlIHJlZ3VsYXIgbG0gbW9kZWwgd2FzIHJhdGhlciBoaWdoLCB3ZSBkZWNpZGVkIHRvIG9ubHkgY29tcGFyZSB0aGUgTEFTU08gYW5kIHJhbmRvbSBmb3Jlc3QgbW9kZWxzIG1vdmluZyBmb3J3YXJkLiBXZSBjb21wdXRlZCB0aGVpciBvdmVyYWxsIHBlcmZvcm1hbmNlIG1ldHJpY3MgYW5kIGxvb2tlZCBhdCB0aGUgcmVzaWR1YWxzLgoKYGBge3J9Cmxhc3NvX2V4cGxhaW4gPC0gCiAgZXhwbGFpbl90aWR5bW9kZWxzKAogICAgbW9kZWwgPSBtbl9sYXNzb19maW5hbF9tb2QsCiAgICBkYXRhID0gbW5fdHJhaW5pbmcgJT4lIHNlbGVjdCgtVG90YWxDbGFzc2VzKSwgCiAgICB5ID0gbW5fdHJhaW5pbmcgJT4lICBwdWxsKFRvdGFsQ2xhc3NlcyksCiAgICBsYWJlbCA9ICJsYXNzbyIKICApCmBgYAoKYGBge3J9CnJmX2V4cGxhaW4gPC0gCiAgZXhwbGFpbl90aWR5bW9kZWxzKAogICAgbW9kZWwgPSBtbl9yZl9maXQsCiAgICBkYXRhID0gbW5fdHJhaW5pbmcgJT4lIHNlbGVjdCgtVG90YWxDbGFzc2VzKSwgCiAgICB5ID0gbW5fdHJhaW5pbmcgJT4lICBwdWxsKFRvdGFsQ2xhc3NlcyksCiAgICBsYWJlbCA9ICJyZiIKICApCmBgYAoKYGBge3J9CiMgZ2V0IG92ZXJhbGwgcGVyZm9ybWFuY2UgbWV0cmljcwpsYXNzb19tb2RfcGVyZiA8LSBtb2RlbF9wZXJmb3JtYW5jZShsYXNzb19leHBsYWluKQpyZl9tb2RfcGVyZiA8LSAgbW9kZWxfcGVyZm9ybWFuY2UocmZfZXhwbGFpbikKYGBgCgpIZXJlIGFyZSB0YWJsZXMgb2YgdGhlaXIgcGVyZm9ybWFuY2UgbWV0cmljczoKCmBgYHtyfQojIExBU1NPCmRhdGEuZnJhbWUobGFzc29fbW9kX3BlcmYkbWVhc3VyZXMpCiMgcmFuZG9tIGZvcmVzdApkYXRhLmZyYW1lKHJmX21vZF9wZXJmJG1lYXN1cmVzKQpgYGAKCkhlcmUgaXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgcmVzaWR1YWxzOgoKYGBge3J9CnBsb3QobGFzc29fbW9kX3BlcmYsCiAgICAgcmZfbW9kX3BlcmYsIAogICAgIGdlb20gPSAiYm94cGxvdCIpCmBgYAoKCioqVmFyaWFibGUgaW1wb3J0YW5jZSoqCgpJbiB0aGUgZW5kLCB0aGUgcmFuZG9tIGZvcmVzdCBzaG93ZWQgdG8gZ3JlYXRseSBvdXRwZXJmb3JtIHRoZSBMQVNTTy4gT3VyIGZpbmFsIHN0ZXAgd2FzIHRvIGxvb2sgYXQgdGhlIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdCBmcm9tIHRoaXMgZmluYWwgbW9kZWwuCgpgYGB7cn0Kc2V0LnNlZWQoMSkgCiMgY3JlYXRlIGV4cGxhaW5lcgpyZl9leHBsYWluIDwtIAogIGV4cGxhaW5fdGlkeW1vZGVscygKICAgIG1vZGVsID0gbW5fcmZfZml0LAogICAgZGF0YSA9IG1uX3RyYWluaW5nICU+JSBzZWxlY3QoLVRvdGFsQ2xhc3NlcyksIAogICAgeSA9IG1uX3RyYWluaW5nICU+JSAgcHVsbChUb3RhbENsYXNzZXMpLAogICAgbGFiZWwgPSAicmYiCiAgKQojIGNvbXB1dGUgdmFyaWFibGUgaW1wb3J0YW5jZQpyZl92YXJfaW1wIDwtIG1vZGVsX3BhcnRzKHJmX2V4cGxhaW4pCmBgYAoKYGBge3IgcGxvdCwgZWNobz1GQUxTRSwgZmlnLmFsaWduPSJjZW50ZXIiLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTJ9CiMgcGxvdApwbG90KHJmX3Zhcl9pbXAsIHNob3dfYm94cGxvdHMgPSBUUlVFKQpgYGAKCiMgSW1wbGljYXRpb25zCgpPbmUgbm90ZSB0aGF0IGlzIGNyaXRpY2FsIHRvIGtlZXAgaW4gbWluZCBpcyB0aGF0IGNvcnJlbGF0aW9uIGRvZXMgbm90IGltcGx5IGNhdXNhdGlvbi4gQWx0aG91Z2ggdGhpcyBwcm9qZWN0IGxvb2tzIGF0IGNvbm5lY3Rpb25zIGJldHdlZW4gdmFyaW91cyBkYXRhIHNldHMgYW5kIGRpZmZlcmVudCB2YXJpYWJsZXMsIHdlIGFyZSBub3Qgc3VnZ2VzdGluZyB0aGF0IGFueSBvZiBvdXIgcHJlZGljdG9ycyBkaXJlY3RseSBhbHRlcnMgY29tcHV0ZXIgc2NpZW5jZSBjb3Vyc2UgYXZhaWxhYmlsaXR5LiBJdCBpcyBwb3NzaWJsZSB0aGF0IHRoYXQgaXMgdGhlIGNhc2UsIGdpdmVuIHRoZSB3b3JrIHRoYXQgd2UgaGF2ZSBkb25lLCBidXQgd2l0aG91dCBhbiBleHBlcmltZW50IG9yIGFjY291bnRpbmcgZm9yIHBvdGVudGlhbCBjb25mb3VuZGVycyAob3RoZXIgdmFyaWFibGVzIHRoYXQgbWF5IGFmZmVjdCB0aGUgcHJlZGljdG9yIGFuZCBvdXRjb21lIHZhcmlhYmxlcyksIHdlIGNhbm5vdCBiZSBjZXJ0YWluIGFib3V0IGNhdXNhdGlvbi4KClByZXZpb3VzIHdvcmsgZG9lcyBleGlzdCBhYm91dCBob3cgZGlzcGFyaXRpZXMgaW4gZWR1Y2F0aW9uIGFyZSByZWxhdGVkIHRvIG1hbnkgb2YgdGhlIHZhcmlhYmxlcyB3ZSBkaXNwbGF5ZWQgaW4gb3VyIHByb2plY3QsIHN1Y2ggYXMgaG91c2Vob2xkIGluY29tZSBhbmQgcmFjZS4gRm9yIGluc3RhbmNlLCBpdCBoYXMgYmVlbiBwcm92ZW4gdGhhdCBBQ1QgYW5kIHN0YW5kYXJkaXplZCB0ZXN0IHNjb3JlcyBzaG93IG1vcmUgYWJvdXQgZmFtaWx5IHdlYWx0aCBhbmQgcHJpdmlsZWdlIHRoYW4gYWN0dWFsIGludGVsbGlnZW5jZSBvciBsaWtlbGlob29kIGZvciBzdWNjZXNzLiBUaGVyZWZvcmUsIGl0IGlzIGxvZ2ljYWwgdGhhdCB0aGUgc2FtZSBkaXN0cmljdHMgdGhhdCBoYXZlIGhpZ2ggYXZlcmFnZSBBQ1Qgc2NvcmVzIHdpbGwgaGF2ZSBoaWdoIG1lZGlhbiBob3VzZWhvbGQgaW5jb21lcyBkdWUgdG8gc3lzdGVtaWMgaW5lcXVhbGl0eS4gRHVlIHRvIHRoZSBmYWN0IHRoYXQgY29tcHV0ZXIgc2NpZW5jZSBpcyBhIG5ld2VyIGZpZWxkLCBsZXNzIHdvcmsgaGFzIGJlZW4gZG9uZSBzcGVjaWZpY2FsbHkgYWJvdXQgdGhpcyBzdWJqZWN0LiBUaGUgcmlzZSBpbiBhdmFpbGFibGUgbGl0ZXJhdHVyZSBvbiB0aGlzIHN1YmplY3QgaW4gcmVjZW50IHllYXJzIGhhcyBhbHNvIGJlZW4gZm9jdXNlZCBtb3JlIG9uIGNvbGxlZ2UgYW5kIGdyYWR1YXRlIHNjaG9vbCwgd2l0aCBLLTEyIGVkdWNhdGlvbiByZWNlaXZpbmcgbGVzcyBhdHRlbnRpb24uCgpUaGF0IGJlaW5nIHNhaWQsIHRoZXJlIGFyZSBtYW55IG51YW5jZXMgdG8gdGhpcyBpc3N1ZSBvZiBjb3Vyc2UgYXZhaWxhYmlsaXR5IGFuZCBpbmVxdWFsaXR5IHRoYXQgd2UgY291bGQgbm90IGFkZHJlc3Mgd2l0aGluIHRoZSBzY29wZSBvZiBvdXIgcHJvamVjdC4gT25lIHZhcmlhYmxlIHdlIGxvb2tlZCBhdCB3YXMgcmFjZSwgYW5kIHdoaWxlIHRoZSBjb25uZWN0aW9uIGJldHdlZW4gcmFjZSBhbmQgZWR1Y2F0aW9uYWwgZGlzcGFyaXRpZXMgaGFzIGJlZW4gc3R1ZGllZCwgdGhhdCBjYW4gYmUgZGlmZmljdWx0IHRvIHNlZSBpbiBzb21lIG9mIG91ciB3b3JrLiBXZSBoeXBvdGhlc2l6ZSB0aGF0IHRoZXJlIGFyZSBhIGZldyByZWFzb25zIGZvciB0aGlzLiBGaXJzdCwgTWlubmVzb3RhIGluIGdlbmVyYWwgaXMgbGFyZ2VseSBwb3B1bGF0ZWQgYnkgV2hpdGUgcGVvcGxlLiBGdXJ0aGVybW9yZSwgdGhlIHBsYWNlcyB3aXRoIHRoZSBtb3N0IHJhY2lhbCBkaXZlcnNpdHkgKG5lYXIgdGhlIFR3aW4gQ2l0aWVzKSwgYXJlIGFsc28gcGxhY2VzIHdpdGggY29uc2lkZXJhYmxlIGluZXF1YWxpdHkuIFdpdGhvdXQgdGhpcyBpbmZvcm1hdGlvbiwgaXQgbWF5IHNlZW0gYXMgdGhvdWdoIHRoZXJlIGlzIGNvcnJlbGF0aW9uIGJldHdlZW4gZ3JlYXRlciBkaXZlcnNpdHksIGhpZ2hlciBtZWRpYW4gaG91c2Vob2xkIGluY29tZSwgYW5kIGNvbXB1dGVyIHNjaWVuY2UgY291cnNlIGF2YWlsYWJpbGl0eS4gSG93ZXZlciwgd2UgY2Fubm90IG1ha2UgdGhpcyBjbGFpbSB3aXRob3V0IGZ1cnRoZXIgaW52ZXN0aWdhdGluZyBob3cgdGhlIGluZXF1YWxpdGllcyB3aXRoaW4gZWFjaCBkaXN0cmljdCBwbGF5IGEgcm9sZS4KCkFsb25nIHdpdGggdGhhdCwgdGhlIHBvcHVsYXRpb24gc2l6ZSBvZiB0aGUgZGlzdHJpY3RzIGNvdWxkIGFmZmVjdCB0aGUgb3V0Y29tZXMuIERpc3RyaWN0cyBjYW4gZW5jb21wYXNzIG1hbnkgc2Nob29scywgYW5kIGl0IGlzIHBvc3NpYmxlIHRoYXQgd2l0aGluIGEgZGlzdHJpY3QgdGhlcmUgaXMgdmFyaWF0aW9uIGluIGRlbW9ncmFwaGljcyBhbmQgY291cnNlIGF2YWlsYWJpbGl0eS4gRnV0dXJlIHdvcmsgbWlnaHQgaW5jbHVkZSBpbnZlc3RpZ2F0aW5nIGEgc21hbGxlciByZWdpb24gdG8gZXhwbG9yZSBzb21lIG9mIHRoZXNlIG51YW5jZXMgaW4gb3JkZXIgdG8gYmV0dGVyIHVuZGVyc3RhbmQgdGhlIGNvbm5lY3Rpb24gYmV0d2VlbiBjb21wdXRlciBzY2llbmNlIGNvdXJzZSBhdmFpbGFiaWxpdHkgaW4gSy0xMiBlZHVjYXRpb24gYW5kIG91ciBvdGhlciB2YXJpYWJsZXMuCg==